[gbrainy] Tool to assist to spot translations with mismatching string formatters and expression variables



commit e8fc19bec7a2679ad00b6d134733a39ff43d04e5
Author: Jordi Mas <jmas softcatala org>
Date:   Sun Oct 31 16:50:40 2010 +0100

    Tool to assist to spot translations with mismatching string formatters and expression variables

 tools/GetTextParser/CatalogParser.cs  |  344 +++++++++++++++++++++++++++++++++
 tools/GetTextParser/StringEscaping.cs |  270 ++++++++++++++++++++++++++
 tools/Makefile.am                     |    8 +
 tools/TranslationsChecker.cs          |  172 ++++++++++++++++
 4 files changed, 794 insertions(+), 0 deletions(-)
---
diff --git a/tools/GetTextParser/CatalogParser.cs b/tools/GetTextParser/CatalogParser.cs
new file mode 100644
index 0000000..303c8f4
--- /dev/null
+++ b/tools/GetTextParser/CatalogParser.cs
@@ -0,0 +1,344 @@
+//
+// CatalogParser.cs
+//
+// Author:
+//   David Makovsk� <yakeen sannyas-on net>
+//
+// Copyright (C) 1999-2006 Vaclav Slavik (Code and design inspiration - poedit.org)
+// Copyright (C) 2007 David Makovsk�
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// 
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+
+namespace MonoDevelop.Gettext
+{
+	public abstract class CatalogParser
+	{
+		//string fileName;
+		string loadedFile;
+		string[] fileLines;
+		string newLine;
+		int lineNumber = 0;
+		
+		public CatalogParser (string fileName, Encoding encoding)
+		{
+			//this.fileName = fileName;
+			loadedFile = File.ReadAllText (fileName, encoding);
+			fileLines = CatalogParser.GetLines (loadedFile, out newLine);
+		}
+		
+		// Returns new line constant used in file
+		public string NewLine
+		{
+			get { return newLine; }
+		}
+		
+		static string[] GetLines (string fileStr, out string newLine)
+		{
+			// TODO: probe first new line...
+			int posLN = fileStr.IndexOf ('\n');
+			int posCR = fileStr.IndexOf ('\r');
+			string useNewLineForParsing = String.Empty;
+			newLine = String.Empty;
+			
+			if (posLN != -1) {
+				if (posCR - 1 == posLN) {
+					newLine = "\r\n"; //CRLF
+				} else if (posCR == -1) {
+					newLine = "\n"; //LF
+				}
+			} else if (posCR != -1 && posLN == -1) {
+				newLine = "\r"; //LF
+			} else {
+				newLine = Environment.NewLine; //mixed for writing use system one
+				int countLN = 0;
+				int start = 0;
+				while ((start = fileStr.IndexOf ('\n', start)) != -1)
+					countLN++;
+				
+				int countCR = 0;
+				start = 0;
+				while ((start = fileStr.IndexOf ('\r', start)) != -1)
+					countCR++;
+					
+				// for parsing use one with more occurences
+				useNewLineForParsing = countCR > countLN ? "\r" : "\n";
+			}
+			
+			if (useNewLineForParsing == String.Empty)
+				useNewLineForParsing = newLine;
+			
+			List<string> lines = new List<string> ();
+			
+			foreach (string line in fileStr.Split (new string[] {useNewLineForParsing}, StringSplitOptions.None)) {
+				lines.Add (line);
+			}
+			return lines.ToArray ();
+		}
+		
+		// If input begins with pattern, fill output with end of input (without
+		// pattern; strips trailing spaces) and return true.  Return false otherwise
+		// and don't touch output
+		static bool ReadParam (string input, string pattern, out string output)
+		{
+			output = String.Empty;
+			input = input.TrimStart (' ', '\t');
+			if (input.Length < pattern.Length)
+				return false;
+			
+			if (! input.StartsWith (pattern))
+				return false;
+			
+			output = StringEscaping.FromGettextFormat (input.Substring (pattern.Length).TrimEnd (' ', '\t'));
+			return true;
+		}
+		
+		string ParseMessage (ref string line, ref string dummy, ref int lineNumber)
+		{
+			StringBuilder result = new StringBuilder (dummy.Substring (0, dummy.Length - 1));
+			
+			while ((line = fileLines[lineNumber++]) != String.Empty) {
+				if (line[0] == '\t') 
+					line = line.Substring (1);
+				
+				if (line[0] == '"' && line[line.Length - 1] == '"') { 
+					result.Append (StringEscaping.FromGettextFormat (line.Substring (1, line.Length - 2)));
+				} else
+					break;
+			}
+			return result.ToString ();
+		}
+		
+		// Parses the entire file, calls OnEntry each time msgid/msgstr pair is found.
+		// return false if parsing failed, true otherwise
+		public bool Parse ()
+		{
+			if (fileLines.Length == 0)
+				return false;
+			
+			string line, dummy;
+			string mflags = String.Empty;
+			string mstr = String.Empty;
+			string msgidPlural = String.Empty;
+			string mcomment = String.Empty;
+			List<string> mrefs = new List<string> ();
+			List<string> mautocomments = new List<string> ();
+			List<string> mtranslations = new List<string> ();
+			bool hasPlural = false;
+			
+			line = fileLines[lineNumber++];
+			if (line == String.Empty)
+				line = fileLines[lineNumber++];
+			
+			while (line != String.Empty)
+			{
+				// ignore empty special tags (except for automatic comments which we
+				// DO want to preserve):
+				while (line == "#," || line == "#:")
+					line = fileLines[lineNumber++];
+				
+				// flags:
+				// Can't we have more than one flag, now only the last is kept ...
+				if (CatalogParser.ReadParam (line, "#, ", out dummy))
+				{
+					mflags = dummy; //"#, " +
+					line = fileLines[lineNumber++];
+				}
+				
+				// auto comments:
+				if (CatalogParser.ReadParam (line, "#. ", out dummy) || CatalogParser.ReadParam (line, "#.", out dummy)) // second one to account for empty auto comments
+				{
+					mautocomments.Add (dummy);
+					line = fileLines[lineNumber++];
+				}
+				
+				// references:
+				else if (CatalogParser.ReadParam (line, "#: ", out dummy))
+				{
+					// A line may contain several references, separated by white-space.
+					// Each reference is in the form "path_name:line_number"
+					// (path_name may contain spaces)
+					dummy = dummy.Trim ();
+					while (dummy != String.Empty) {
+						int i = 0;
+						while (i < dummy.Length && dummy[i] != ':') {
+							i++;
+						}
+						while (i < dummy.Length && ! Char.IsWhiteSpace (dummy[i])) {
+							i++;
+						}
+						
+						mrefs.Add (dummy.Substring (0, i));
+						dummy = dummy.Substring (i).Trim ();
+					}
+					
+					line = fileLines[lineNumber++];
+				}
+				
+				// msgid:
+				else if (CatalogParser.ReadParam (line, "msgid \"", out dummy) ||
+				         CatalogParser.ReadParam (line, "msgid\t\"", out dummy))
+				{
+					mstr = ParseMessage (ref line, ref dummy, ref lineNumber);
+				}
+				
+				// msgid_plural:
+				else if (CatalogParser.ReadParam (line, "msgid_plural \"", out dummy) ||
+				         CatalogParser.ReadParam (line, "msgid_plural\t\"", out dummy))
+				{
+					msgidPlural = ParseMessage (ref line, ref dummy, ref lineNumber);
+					hasPlural = true;
+				}
+
+				// msgstr:
+				else if (CatalogParser.ReadParam (line, "msgstr \"", out dummy) ||
+				         CatalogParser.ReadParam (line, "msgstr\t\"", out dummy))
+				{
+					if (hasPlural) {
+						// TODO: use logging
+						Console.WriteLine ("Broken catalog file: singular form msgstr used together with msgid_plural");
+						return false;
+					}
+					
+					
+					string str = ParseMessage (ref line, ref dummy, ref lineNumber);
+					mtranslations.Add (str);
+					
+					if (! OnEntry (mstr, String.Empty, false, mtranslations.ToArray (),
+					               mflags, mrefs.ToArray (), mcomment,
+					               mautocomments.ToArray ()))
+					{
+						return false;
+					}
+					
+					mcomment = mstr = msgidPlural = mflags = String.Empty;
+					hasPlural = false;
+					mrefs.Clear ();
+					mautocomments.Clear ();
+					mtranslations.Clear ();
+				} else if (CatalogParser.ReadParam (line, "msgstr[", out dummy)) {
+					// msgstr[i]:
+					if (!hasPlural){
+						// TODO: use logging
+						Console.WriteLine ("Broken catalog file: plural form msgstr used without msgid_plural");
+						return false;
+					}
+					
+					int pos = dummy.IndexOf (']');
+					string idx = dummy.Substring (pos - 1, 1);
+					string label = "msgstr[" + idx + "]";
+					
+					while (CatalogParser.ReadParam (line, label + " \"", out dummy) || CatalogParser.ReadParam (line, label + "\t\"", out dummy)) {
+						StringBuilder str = new StringBuilder (dummy.Substring (0, dummy.Length - 1));
+						
+						while ((line = fileLines[lineNumber++]) != String.Empty) {
+							if (line[0] == '\t')
+								line = line.Substring (1);
+							if (line[0] == '"' && line[line.Length - 1] == '"') {
+								str.Append (line.Substring (1, line.Length - 2));
+							} else {
+								if (ReadParam (line, "msgstr[", out dummy)) {
+									pos = dummy.IndexOf (']');
+									idx = dummy.Substring (pos - 1, 1);
+									label = "msgstr[" + idx + "]";
+								}
+								break;
+							}
+						}
+						mtranslations.Add (str.ToString ());
+					}
+					
+					if (! OnEntry (mstr, msgidPlural, true, mtranslations.ToArray (),
+					               mflags, mrefs.ToArray (), mcomment,
+					               mautocomments.ToArray ()))
+					{
+						return false;
+					}
+					
+					mcomment = mstr = msgidPlural = mflags = String.Empty;
+					hasPlural = false;
+					mrefs.Clear ();
+					mautocomments.Clear ();
+					mtranslations.Clear ();
+				}else if (CatalogParser.ReadParam (line, "#~ ", out dummy)) {
+					// deleted lines:
+					
+					List<string> deletedLines = new List<string> ();
+					deletedLines.Add (line);
+					while ((line = fileLines[lineNumber++]) != String.Empty) {
+						// if line does not start with "#~ " anymore, stop reading
+						if (! ReadParam (line, "#~ ", out dummy))
+							break;
+
+						deletedLines.Add (line);
+					}
+					if (! OnDeletedEntry (deletedLines.ToArray (), mflags, null, mcomment, mautocomments.ToArray ())) 
+						return false;
+					
+					mcomment = mstr = msgidPlural = mflags = String.Empty;
+					hasPlural = false;
+					mrefs.Clear ();
+					mautocomments.Clear ();
+					mtranslations.Clear ();
+				} else if (line[0] == '#') {
+					// comment:
+					
+					while (line != String.Empty &&
+					       ((line[0] == '#' && line.Length < 2) ||
+						   (line[0] == '#' && line[1] != ',' && line[1] != ':' && line[1] != '.')))
+					{
+						mcomment += mcomment.Length > 0 ? '\n' + line : line;
+						line = fileLines[lineNumber++];
+					}
+				} else {
+					line = fileLines[lineNumber++];
+					
+				}
+				
+				while (line == String.Empty && lineNumber < fileLines.Length)
+					line = fileLines[lineNumber++];
+			}
+			
+			return true;
+		}
+		
+		// Called when new entry was parsed. Parsing continues
+		// if returned value is true and is cancelled if it is false.
+		protected abstract bool OnEntry (string msgid, string msgidPlural, bool hasPlural,
+		                                 string[] translations, string flags,
+		                                 string[] references, string comment,
+		                                 string[] autocomments);
+
+		// Called when new deleted entry was parsed. Parsing continues
+		// if returned value is true and is cancelled if it
+		// is false. Defaults to an empty implementation.
+		protected virtual bool OnDeletedEntry (string[] deletedLines, string flags,
+		                                       string[] references, string comment,
+		                                       string[] autocomments)
+		{
+			return true;
+		}
+	}
+}
diff --git a/tools/GetTextParser/StringEscaping.cs b/tools/GetTextParser/StringEscaping.cs
new file mode 100644
index 0000000..b68c3b4
--- /dev/null
+++ b/tools/GetTextParser/StringEscaping.cs
@@ -0,0 +1,270 @@
+// 
+// StringEscaping.cs
+// 
+// Author:
+//   Michael Hutchinson <mhutchinson novell com>
+// 
+// Copyright (C) 2008 Novell, Inc (http://www.novell.com)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace MonoDevelop.Gettext
+{
+	
+	
+	public static class StringEscaping
+	{
+		//based on http://www-ccs.ucsd.edu/c/charset.html
+		//with modififications, as Gettext dosn't follow all C escaping
+		public static string ToGettextFormat (string text)
+		{
+			StringBuilder sb = new StringBuilder ();
+			for (int i = 0; i < text.Length; i++) {
+				char c = text[i];
+				switch (c) {
+				case '"':
+					sb.Append ("\\\"");
+					continue;
+				
+				//Gettext doesn't like escaping this
+				//case '\'':
+				//	sb.Append ("\\'");
+				//	continue;
+				
+				//These shouldn't be in translations... but will be caught by IsControl
+				//case '\a':
+				//case '\b':
+				//case '\f':
+				//case '\v':
+					
+				//this doesn't matter since we're not dealing with trigraphs
+				//case "?":
+				//	sb.Append ("\\?");
+				//	continue;
+					
+				case '\\':
+					sb.Append ("\\\\");
+					continue;
+				case '\n':
+					sb.Append ("\\n");
+					continue;
+				case '\r':
+					sb.Append ("\\r");
+					continue;
+				case '\t':
+					sb.Append ("\\t");
+					continue;
+				}
+
+				if (c != '_' && char.IsControl (c))
+					throw new FormatException (String.Format (("Invalid character '{0}' in translatable string: '{1}'"),
+					                                          c,
+					                                          text));
+				
+				sb.Append (c);
+			}
+			return sb.ToString ();
+		}
+		
+		public static string FromGettextFormat (string text)
+		{
+			StringBuilder sb = new StringBuilder ();
+			for (int i = 0; i < text.Length; i++) {
+				char c = text[i];
+				switch (c) {
+				case '\\':
+					if (i + 1 < text.Length) {
+						char nextChar = text [i + 1];
+						if (nextChar == '\\' || nextChar == '"') {
+							sb.Append (nextChar);
+							i++;
+							continue;
+						}
+						if (nextChar == 'n') {
+							sb.Append ('\n');
+							i++;
+							continue;
+						}
+						if (nextChar == 't') {
+							sb.Append ('\t');
+							i++;
+							continue;
+						}
+						if (nextChar == 'r') {
+							sb.Append ('\r');
+							i++;
+							continue;
+						}
+
+						throw new FormatException (String.Format ("Invalid escape sequence '{0}' in string: '{1}'",
+						                                          nextChar,
+						                                          text));
+					}
+					break;
+				}
+				
+				sb.Append (c);
+			}
+			return sb.ToString ();
+		}
+		
+		public static string UnEscape (EscapeMode mode, string text)
+		{
+			switch (mode) {
+			case EscapeMode.None:
+				return text;
+			case EscapeMode.CSharp:
+				return FromCSharpFormat (text);
+			case EscapeMode.CSharpVerbatim:
+				return FromCSharpVerbatimFormat (text);
+			case EscapeMode.Xml:
+				return FromXml (text);
+			default:
+				throw new Exception ("Unknown string escaping mode '" + mode.ToString () + "'");
+			}
+		}
+		
+		/*
+		//based on http://www-ccs.ucsd.edu/c/charset.html 
+		public static string FromCFormat (string text)
+		{
+		}
+		*/
+		
+		//based on the C# 2.0 spec
+		static string FromCSharpVerbatimFormat (string text)
+		{
+			StringBuilder sb = new StringBuilder ();
+			for (int i = 0; i < text.Length; i++) {
+				char c1 = text[i];
+				if (c1 == '"') {
+					i++;
+					char c2 = text[i];
+					if (c2 != '"')
+						throw new FormatException ("Unescaped '\"' character in C# verbatim string.");
+				}
+				sb.Append (c1);
+			}
+			return sb.ToString ();	
+		}
+		
+		static string FromXml (string text)
+		{
+			StringBuilder sb = new StringBuilder ();
+			for (int i = 0; i < text.Length; i++) {
+				char c1 = text[i];
+				if (c1 == '&') {
+					int end = text.IndexOf (';', i);
+					if (end == -1)
+						throw new FormatException ("Unterminated XML entity.");
+					string entity = text.Substring (i+1, end - i - 1);
+					switch (entity) {
+					case "lt":
+						sb.Append ('<');
+						break;
+					case "gt":
+						sb.Append ('>');
+						break;
+					case "amp":
+						sb.Append ('&');
+						break;
+					case "apos":
+						sb.Append ('\'');
+						break;
+					case "quot":
+						sb.Append ('"');
+						break;
+					default:
+						throw new FormatException ("Unrecogised XML entity '&" + entity + ";'.");
+					}
+					i = end;
+				} else
+					sb.Append (c1);
+			}
+			return sb.ToString ();	
+		}
+		
+		//based on the C# 2.0 spec
+		static string FromCSharpFormat (string text)
+		{
+			StringBuilder sb = new StringBuilder ();
+			for (int i = 0; i < text.Length; i++) {
+				char c1 = text[i];
+				if (c1 != '\\') {
+					sb.Append (c1);
+					continue;
+				}
+				
+				i++;
+				char c2 = text[i];
+				
+				switch (c2) {
+				case '\'':
+				case '"':
+				case '\\':
+					sb.Append (c2);
+					break;
+				case 'n':
+					sb.Append ('\n');
+					break;
+				case 'r':
+					sb.Append ('\r');
+					break;
+				case 't':
+					sb.Append ('\t');
+					break;
+				case 'U':
+					//FIXME UNICODE
+					//break;
+				case 'u':
+					//FIXME unicode
+					//break;
+				case 'x':
+					//FIXME hex unicode
+					//break;
+					//if (char.IsControl (c);
+					
+				//case '0':
+				//case 'a':
+				//case 'b':
+				//case 'f':
+				//case 'v':
+				default:
+					throw new FormatException ("Invalid escape '\\" + c2 + "' in translatable string.");
+				}
+				
+			}
+			return sb.ToString ();
+		}
+		
+		public enum EscapeMode
+		{
+			None,
+			CSharp,
+			CSharpVerbatim,
+			Xml
+		}
+	}
+}
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 8c99c83..a379e69 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -3,6 +3,11 @@ EXTRAFLAGS = $(CSC_DEFINES)
 GAMESXMLSTRINGS_CSFILES = \
 	$(srcdir)/GameXmlToGetString.cs
 
+TRANSLATIONSCHECKER_CSFILES = \
+	$(srcdir)/TranslationsChecker.cs \
+	$(srcdir)/GetTextParser/CatalogParser.cs \
+	$(srcdir)/GetTextParser/StringEscaping.cs
+
 ASSEMBLIES = \
 	-r:../src/gbrainy.Core.dll	\
 	-r:Mono.Posix
@@ -11,6 +16,9 @@ GameXmlToGetString.exe: $(GAMESXMLSTRINGS_CSFILES) ../data/games.xml $(srcdir)/G
 		   $(CSC) -target:winexe -out:$@ $(EXTRAFLAGS) $(GAMESXMLSTRINGS_CSFILES) $(ASSEMBLIES)
 		   export MONO_PATH=../src && $(MONO) $@ $(srcdir)
 
+TranslationsChecker.exe: $(TRANSLATIONSCHECKER_CSFILES)
+		   $(CSC) -target:winexe -out:$@ $(EXTRAFLAGS) $(TRANSLATIONSCHECKER_CSFILES) $(ASSEMBLIES)
+
 all: GameXmlToGetString.exe
 
 EXTRA_DIST = $(GAMESXMLSTRINGS_CSFILES) $(srcdir)/GameXmlGetStringTemplate.cs  $(srcdir)/GameXmlGetString.cs
diff --git a/tools/TranslationsChecker.cs b/tools/TranslationsChecker.cs
new file mode 100644
index 0000000..21de2d8
--- /dev/null
+++ b/tools/TranslationsChecker.cs
@@ -0,0 +1,172 @@
+/*
+ * 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 MonoDevelop.Gettext;
+using System.Text.RegularExpressions;
+
+public class GameXmlToGetString
+{
+	public class Parser : CatalogParser
+	{
+		public Parser (string fileName, Encoding encoding) : base (fileName, encoding)
+		{
+
+		}
+
+		List <string> GetExpressionVariables (string str)
+		{
+			Regex regex;
+			Match match;
+			List <string> strs = new List <string> ();
+			string expression = "(\\[[a-z0-9._%+-]*\\])+";  // alike [age]
+
+			regex = new Regex (expression, RegexOptions.IgnoreCase);
+			match = regex.Match (str);
+
+			if (String.IsNullOrEmpty (match.Value) == false)
+			{
+				while (String.IsNullOrEmpty (match.Value) == false)
+				{
+					strs.Add (match.Value);
+					match = match.NextMatch ();
+				}
+			}
+			return strs;
+		}
+
+		List <string> GetStringFormaters (string str)
+		{
+			Regex regex;
+			Match match;
+			List <string> strs = new List <string> ();
+			string expression = "(\\{[a-z0-9.:_%+-]*\\})+";  // alike [age]
+
+			regex = new Regex (expression, RegexOptions.IgnoreCase);
+			match = regex.Match (str);
+
+			if (String.IsNullOrEmpty (match.Value) == false)
+			{
+				while (String.IsNullOrEmpty (match.Value) == false)
+				{
+					strs.Add (match.Value);
+					match = match.NextMatch ();
+				}
+			}
+			return strs;
+		}
+
+
+		int Count (List <string> strings, string str)
+		{
+			int cnt = 0;
+
+			foreach (string s in strings)
+			{
+				if (s == str)
+					cnt++;
+			}
+			return cnt;
+
+		}
+
+		protected override bool OnEntry (string msgid, string msgidPlural, bool hasPlural,
+		                         string[] translations, string flags,
+		                         string[] references, string comment,
+		                         string[] autocomments)
+		{
+
+			if (String.IsNullOrEmpty (translations [0]))
+				return true;
+
+			// This string uses [] but not variables expressions
+			if (msgid == "Usage: gbrainy [options]")
+				return true;
+
+			if (flags.IndexOf ("fuzzy") != -1)
+				return true;
+
+			// Check Expression variables (like [age])
+			List <string> source = GetExpressionVariables (msgid);
+
+			for (int i = 0; i < translations.Length; i++)
+			{
+				List <string> target = GetExpressionVariables (translations [i]);
+
+				foreach (string s in source)
+				{
+					if (Count (source, s) != Count (target, s))
+					{
+						Console.WriteLine ("Gbrainy expression variable error. In '{0}' string '{1}' count does not match", msgid, s);
+					}
+				}
+			}
+
+			// Check Formatters (like {0})
+			source = GetStringFormaters (msgid);
+
+			for (int i = 0; i < translations.Length; i++)
+			{
+				List <string> target = GetStringFormaters (translations [i]);
+
+				foreach (string s in source)
+				{
+					if (Count (source, s) != Count (target, s))
+					{
+						Console.WriteLine ("String Formatter error. In '{0}' string '{1}' count does not match", msgid, s);
+					}
+				}
+			}
+
+			return true;
+		}
+	}
+
+	/*
+		This tool scans the LINGUAS files and searches for potential
+		mismatching string formatters and expression variables that can
+		cause problems at runtime.
+	*/
+	static void Main (string[] args)
+	{
+		string line, file;
+		Stream read = File.OpenRead ("../po/LINGUAS");
+		StreamReader sr = new StreamReader (read);
+
+		while (true) {
+			line = sr.ReadLine ();
+			if (line == null)
+				break;
+
+			if (line.StartsWith ("#") == true)
+				continue;
+
+			file = "../po/" + line + ".po";
+
+			Console.WriteLine ("Openning {0}", file);
+			Parser parser = new Parser (file, Encoding.UTF8);
+			parser.Parse ();
+		}
+	}
+}
+



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