diff --git a/XenAdmin/XenAdmin.csproj b/XenAdmin/XenAdmin.csproj
index 44872c5dd..e36666ae2 100644
--- a/XenAdmin/XenAdmin.csproj
+++ b/XenAdmin/XenAdmin.csproj
@@ -1088,7 +1088,6 @@
Component
-
UserControl
diff --git a/devtools/ResxCheck/Program.cs b/devtools/ResxCheck/Program.cs
new file mode 100644
index 000000000..582a467f4
--- /dev/null
+++ b/devtools/ResxCheck/Program.cs
@@ -0,0 +1,46 @@
+/* Copyright (c) Citrix Systems Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ResxCheck
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ ResxCheck.FindUnusedMessages(@"..\..\..\..",false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/devtools/ResxCheck/Properties/AssemblyInfo.cs b/devtools/ResxCheck/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..13f9817f5
--- /dev/null
+++ b/devtools/ResxCheck/Properties/AssemblyInfo.cs
@@ -0,0 +1,67 @@
+/* Copyright (c) Citrix Systems Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ResxCheck")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ResxCheck")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("8c91561e-914f-4514-91a7-42c22ea012bc")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/XenAdmin/ResxCheck.cs b/devtools/ResxCheck/ResxCheck.cs
similarity index 89%
rename from XenAdmin/ResxCheck.cs
rename to devtools/ResxCheck/ResxCheck.cs
index ac174a544..4b7877e6e 100644
--- a/XenAdmin/ResxCheck.cs
+++ b/devtools/ResxCheck/ResxCheck.cs
@@ -1,450 +1,441 @@
-/* Copyright (c) Citrix Systems Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms,
- * with or without modification, are permitted provided
- * that the following conditions are met:
- *
- * * Redistributions of source code must retain the above
- * copyright notice, this list of conditions and the
- * following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the
- * following disclaimer in the documentation and/or other
- * materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Text.RegularExpressions;
-using System.Text;
-using System.Xml;
-
-
-namespace XenAdmin
-{
- static class ResxCheck
- {
- ///
- /// Produces a list of unused resources in Messages and FriendlyErrorNames.
- ///
- /// Try running from the VS immediate window:
- /// ResxCheck.Run(@"C:\Documents and Settings\hwarrington\xenadmin-unstable.hg\XenAdmin", false)
- ///
- /// If true, will actually purge unused messages from the Messages.resx file.
- public static void FindUnusedMessages(string rootDir, bool removeUnused)
- {
- int totalMessages = 0, totalFriendlyErrorNames = 0;
- List resources = new List();
- foreach (PropertyInfo property in typeof(Messages).GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
- {
- resources.Add("Messages." + property.Name.Trim());
- totalMessages++;
- }
- foreach (PropertyInfo property in typeof(XenAPI.FriendlyErrorNames).GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
- {
- resources.Add("FriendlyErrorNames." + property.Name.Trim());
- totalFriendlyErrorNames++;
- }
-
- // Build file list for project
- List files = new List();
- RecursiveGetCsFiles(new DirectoryInfo(rootDir), files);
- files.RemoveAll((Predicate)delegate(FileInfo f)
- {
- return f.Name.StartsWith("Messages.") || f.Name.StartsWith("FriendlyErrorNames.");
- });
- Console.WriteLine(string.Format("Looking in {0} files", files.Count));
-
- // Now remove resources from the list if they appear in source files
- foreach (FileInfo fileinfo in files)
- {
- string[] lines = File.ReadAllLines(fileinfo.FullName);
- foreach (string line in lines)
- {
- resources.RemoveAll((Predicate)delegate(string resource)
- {
- if (line.Contains(resource))
- {
- //Console.WriteLine(line + " ::: " + resource);
- return true;
- }
- else
- {
- return false;
- }
- });
- }
- }
-
- int messages = 0, friendlyErrorNames = 0;
- foreach (string unused in resources)
- {
- if (unused.StartsWith("Messages."))
- {
- messages++;
- }
- else if (unused.StartsWith("FriendlyErrorNames."))
- {
- friendlyErrorNames++;
- }
- Console.WriteLine(unused);
- }
- Console.WriteLine(string.Format("Messages.resx: {0}/{1} are unused", messages, totalMessages));
- Console.WriteLine(string.Format("FriendlyErrorNames.resx: {0}/{1} are unused", friendlyErrorNames, totalFriendlyErrorNames));
-
- // Remove unused messages from Messages.rex. Note that this method is extremely
- // crude and depends on the exact format of the XML.
- if (removeUnused)
- {
- Console.WriteLine("Removing unused messages from Messages.resx");
-
- List unusedFromMessages = new List();
- foreach (string line in resources)
- {
- if (line.StartsWith("Messages."))
- {
- unusedFromMessages.Add(line.Substring(9));
- }
- }
-
- string path = Path.Combine(rootDir, "Messages.resx");
- XmlDocument doc = new XmlDocument();
- doc.LoadXml(File.ReadAllText(path));
-
- List nodesToRemove = new List();
- foreach (XmlNode node in doc.GetElementsByTagName("data"))
- {
- if (unusedFromMessages.Contains(node.Attributes["name"].Value))
- {
- nodesToRemove.Add(node);
- }
- }
-
- foreach (XmlNode node in nodesToRemove)
- {
- doc.ChildNodes[1].RemoveChild(node);
- }
-
- doc.Save(path);
- }
- }
-
- private static void RecursiveGetCsFiles(DirectoryInfo dir, List files)
- {
- files.AddRange(dir.GetFiles("*.cs"));
- foreach (DirectoryInfo subdir in dir.GetDirectories())
- {
- RecursiveGetCsFiles(subdir, files);
- }
- }
-
- private static void RecursiveGetResxFiles(DirectoryInfo dir, List files)
- {
- files.AddRange(dir.GetFiles("*.resx"));
- foreach (DirectoryInfo subdir in dir.GetDirectories())
- {
- if (subdir.Name == "i18n")
- continue;
- RecursiveGetResxFiles(subdir, files);
- }
- }
-
- private static void FindNodesInJaButNotEn(string rootDir)
- {
- // Find all english resxs
- List enResxFiles = new List();
- RecursiveGetResxFiles(new DirectoryInfo(rootDir), enResxFiles);
-
- foreach (FileInfo enResxFile in enResxFiles)
- {
- string enResxPath = enResxFile.FullName;
- XmlDocument enXml = new XmlDocument();
- enXml.LoadXml(File.ReadAllText(enResxPath));
-
- string jaFilename = enResxPath.Substring(rootDir.Length);
- jaFilename = jaFilename.Insert(jaFilename.Length - 5, ".ja");
- string jaResxPath = rootDir + "\\i18n\\ja" + jaFilename;
- XmlDocument jaXml = new XmlDocument();
- if (!File.Exists(jaResxPath))
- {
- continue;
- }
- jaXml.LoadXml(File.ReadAllText(jaResxPath));
-
- XmlNodeList enDataNodes = enXml.GetElementsByTagName("data");
- XmlNodeList jaDataNodes = jaXml.GetElementsByTagName("data");
-
- List jaDataNodeList = new List();
- foreach (XmlNode jaNode in jaDataNodes)
- {
- jaDataNodeList.Add(jaNode);
- }
-
- List inJaButNotEn = jaDataNodeList.FindAll((Predicate)delegate(XmlNode jaNode)
- {
- string jaDataName = jaNode.Attributes["name"].Value;
- foreach (XmlNode enNode in enDataNodes)
- {
- if (enNode.Attributes["name"].Value == jaDataName)
- {
- return enNode.InnerXml != jaNode.InnerXml;
- }
- }
- return true;
- });
-
- foreach (XmlNode node in inJaButNotEn)
- {
- System.Console.WriteLine(string.Format("'{0}' is in '{1}' but not in '{2}'",
- node.Attributes["name"].Value,
- jaResxPath,
- enResxFile.Name));
- }
- }
- }
-
- private static readonly string[] i18nYes = new string[] { "Text", "ToolTipText", "HeaderText", "AccessibleDescription", "ToolTip", "Filter" };
- private static readonly string[] i18nNo = new string[] { "ZOrder", "Size", "Location", "Anchor", "Type", "MinimumSize", "ClientSize",
- "Font", "TabIndex", "Parent", "LayoutSettings", "Margin", "Padding", "ColumnCount", "Dock", "AutoSize", "Name", "ImeMode",
- "IntegralHeight", "Visible", "InitialImage", "AutoScaleDimensions", "FlowDirection", "RowCount", "ImageAlign", "WrapContents",
- "Enabled", "TextAlign", "StartPosition", "SizeMode", "Multiline", "ScrollBars", "ItemHeight", "CellBorderStyle", "AutoSizeMode",
- "Image", "AutoCompleteCustomSource", "AutoCompleteCustomSource1", "AutoCompleteCustomSource2", "BulletIndent", "Width",
- "MinimumWidth", "AutoScroll", "ImageSize", "MaxLength", "BackgroundImageLayout", "ImageTransparentColor", "ImageIndex",
- "SplitterDistance", "MaximumSize", "ThousandsSeparator", "RightToLeft", "TextImageRelation", "ContentAlignment",
- "SelectedImageIndex", "HorizontalScrollbar", "CheckAlign", "RightToLeftLayout", "ShowShortcutKeys", "ShortcutKeys",
- "ShortcutKeyDisplayString", "Localizable", "Icon", "Menu", "AutoScrollMinSize", "Items", "ScrollAlwaysVisible",
- "Items1", "Items2", "Items3", "MaxDropDownItems" };
- private static bool IsI18nableProperty(string filename, string name)
- {
- foreach (string property in i18nYes)
- {
- if (name.EndsWith("." + property))
- {
- // Keep these tags
- return true;
- }
- }
-
- foreach (string property in i18nNo)
- {
- if (name.EndsWith("." + property))
- {
- // Reject these tags
- return false;
- }
- }
-
- // We haven't seen these tags before - keep them but issue a notification
- Console.WriteLine(filename + ": " + name);
- return true;
- }
-
- private static bool ExcludeResx(string filePath)
- {
- return filePath.EndsWith(@"\Properties\Resources.resx") ||
- filePath.EndsWith(@"\Help\HelpManager.resx") ||
- filePath.EndsWith(@"\DotNetVnc\KeyMap.resx");
- }
-
- ///
- /// Try from the immediate window e.g.
- /// XenAdmin.ResxCheck.TrimJaResxs(@"C:\Documents and Settings\hwarrington\xenadmin-unstable.hg\XenAdmin")
- ///
- ///
- private static void TrimJaResxs(string rootDir)
- {
- // Find all english resxs
- List enResxFiles = new List();
- RecursiveGetResxFiles(new DirectoryInfo(rootDir), enResxFiles);
-
- List names = new List();
-
- foreach (FileInfo enResxFile in enResxFiles)
- {
- if (ExcludeResx(enResxFile.FullName))
- {
- continue;
- }
-
- // Load the en resx
- string enResxPath = enResxFile.FullName;
- XmlDocument enXml = new XmlDocument();
- enXml.LoadXml(File.ReadAllText(enResxPath));
- XmlNodeList enDataNodes = enXml.GetElementsByTagName("data");
-
- // Find the ja resx
- string jaFilename = enResxPath.Substring(rootDir.Length);
- jaFilename = jaFilename.Insert(jaFilename.Length - 5, ".ja");
- string jaResxPath = rootDir + "\\i18n\\ja" + jaFilename;
-
- if (!File.Exists(jaResxPath))
- {
- // There is no ja resx file corresponding to the en resx. We need to check there are no i18nable tags
- // in the en resx.
- bool i18nRequired = false;
- foreach (XmlNode enNode in enDataNodes)
- {
- if (IsI18nableProperty(enResxFile.Name, enNode.Attributes["name"].Value))
- {
- Console.WriteLine(string.Format("{0} is missing. Tag {1} needs i18n. Copying en resx across.", jaFilename, enNode.Attributes["name"].Value));
- Directory.CreateDirectory(Path.GetDirectoryName(jaResxPath));
- File.Copy(enResxPath, jaResxPath);
- i18nRequired = true;
- break;
- }
- }
- if (!i18nRequired)
- {
- continue;
- }
- }
-
- // Load the ja resx
- XmlDocument jaXml = new XmlDocument();
- jaXml.LoadXml(File.ReadAllText(jaResxPath));
- XmlNodeList jaDataNodes = jaXml.GetElementsByTagName("data");
- // Take a copy of the jaDataNodes
- List jaDataNodeList = new List();
- foreach (XmlNode node in jaDataNodes)
- {
- jaDataNodeList.Add(node);
- }
-
- // Go through all the ja nodes, keeping only the ones where their values differ from the en original
- // Don't bother to do this for the messages files.
- if (enResxFile.Name != "Messages.resx" && enResxFile.Name != "FriendlyErrorNames.resx" &&
- enResxFile.Name != "FriendlyNames.resx")
- {
- foreach (XmlNode jaNode in jaDataNodeList)
- {
- string jaDataName = jaNode.Attributes["name"].Value;
-
- if (!IsI18nableProperty(jaFilename, jaDataName))
- {
- // Delete node
- jaXml.GetElementsByTagName("root")[0].RemoveChild(jaNode);
- continue;
- }
-
- foreach (XmlNode enNode in enDataNodes)
- {
- if (enNode.Attributes["name"].Value == jaDataName)
- {
- if (enNode.InnerXml == jaNode.InnerXml)
- {
- // If node unchanged, delete it
- jaXml.GetElementsByTagName("root")[0].RemoveChild(jaNode);
- break;
- }
- }
- }
- }
- }
-
- // Now add any nodes that are in en but not ja (as long as they are of the i18nable types).
- foreach (XmlNode enNode in enDataNodes)
- {
- bool needToAdd = true;
-
- foreach (XmlNode jaNode in jaDataNodes)
- {
- if (enNode.Attributes["name"].Value == jaNode.Attributes["name"].Value)
- {
- needToAdd = false;
- break;
- }
- }
-
- if (needToAdd && IsI18nableProperty(enResxFile.Name, enNode.Attributes["name"].Value))
- {
- XmlNode n = jaXml.GetElementsByTagName("root")[0].AppendChild(jaXml.ImportNode(enNode, true));
- foreach (XmlNode child in n.ChildNodes)
- {
- if (child is XmlWhitespace)
- continue;
- if (child is XmlSignificantWhitespace)
- continue;
- else
- child.InnerText += " (ja)";
- }
- }
- }
-
- XmlWriterSettings settings = new XmlWriterSettings();
- settings.CloseOutput = true;
- settings.Indent = true;
- XmlWriter writer = XmlWriter.Create(jaResxPath, settings);
- jaXml.WriteContentTo(writer);
- writer.Flush();
- writer.Close();
- }
-
- Console.WriteLine("Done");
- }
-
- ///
- /// Checks I haven't screwed stuff up while changing the autogen resx stuff.
- ///
- public static void CheckNotBorked()
- {
- XmlDocument origXml = new XmlDocument();
- origXml.LoadXml(File.ReadAllText(@"C:\Documents and Settings\hwarrington\xenadmin-unstable.hg\XenAdmin\XenAPI\FriendlyNames.resx"));
-
- XmlDocument newXml = new XmlDocument();
- newXml.LoadXml(File.ReadAllText(@"Q:\local\scratch-2\hwarrington\build.hg\myrepos\api.hg\ocaml\idl\csharp_backend\autogen-gui\FriendlyNames.resx"));
-
- CheckAIncludesB(origXml, newXml);
- CheckAIncludesB(newXml, origXml);
- }
-
- private static XmlNode FindByName(XmlDocument doc, string name)
- {
- foreach (XmlNode node in doc.GetElementsByTagName("data"))
- {
- if (name == node.Attributes["name"].Value)
- return node;
- }
- return null;
- }
-
- private static void CheckAIncludesB(XmlDocument origXml, XmlDocument newXml)
- {
- XmlNodeList origDataNodes = origXml.GetElementsByTagName("data");
-
- foreach (XmlNode oldNode in origDataNodes)
- {
- string name = oldNode.Attributes["name"].Value;
- string oldValue = oldNode.InnerXml;
-
- XmlNode newNode = FindByName(newXml, name);
- if (newNode == null)
- {
- throw new Exception(String.Format("Node with name {0} exists in old but not new!", name));
- }
- string newValue = newNode.InnerXml;
- if (newValue != oldValue)
- {
- throw new Exception(String.Format("Node with name {0} has value {1} in old but {2} in new!", name, oldValue, newValue));
- }
- }
- }
- }
-}
+/* Copyright (c) Citrix Systems Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Text;
+using System.Xml;
+
+
+namespace ResxCheck
+{
+ static class ResxCheck
+ {
+ ///
+ /// Produces a list of unused resources in Messages and FriendlyNames.
+ ///
+ /// If true, will actually purge unused messages from the Messages.resx file
+ public static void FindUnusedMessages(string rootDir, bool removeUnused)
+ {
+ Assembly assembly = Assembly.LoadFrom(Path.Combine(rootDir, @"XenModel\bin\Debug\XenModel.dll"));
+
+ int totalMessages = 0, totalFriendlyErrorNames = 0;
+ var resources = new List();
+
+ Type messagesType = assembly.GetType("XenAdmin.Messages");
+ Type friendlyNamesType = assembly.GetType("XenAdmin.FriendlyNames");
+
+ foreach (PropertyInfo property in messagesType.GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
+ {
+ resources.Add("Messages." + property.Name.Trim());
+ totalMessages++;
+ }
+ foreach (PropertyInfo property in friendlyNamesType.GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
+ {
+ resources.Add("FriendlyNames." + property.Name.Trim());
+ totalFriendlyErrorNames++;
+ }
+
+ // Build file list for project
+ List files = new List();
+ RecursiveGetCsFiles(new DirectoryInfo(rootDir), files);
+ files.RemoveAll(f => f.Name.StartsWith("Messages.") || f.Name.StartsWith("FriendlyNames."));
+ Console.WriteLine(string.Format("Looking in {0} files", files.Count));
+
+ // Now remove resources from the list if they appear in source files
+ foreach (FileInfo fileinfo in files)
+ {
+ string[] lines = File.ReadAllLines(fileinfo.FullName);
+ foreach (string line in lines)
+ {
+ string curLine = line;
+ resources.RemoveAll(resource => curLine.Contains(resource));
+ }
+ }
+
+ int messages = 0, friendlyErrorNames = 0;
+ foreach (string unused in resources)
+ {
+ if (unused.StartsWith("Messages."))
+ {
+ messages++;
+ }
+ else if (unused.StartsWith("FriendlyNames."))
+ {
+ friendlyErrorNames++;
+ }
+ Console.WriteLine(unused);
+ }
+ Console.WriteLine(string.Format("Messages.resx: {0}/{1} are unused", messages, totalMessages));
+ Console.WriteLine(string.Format("FriendlyNames.resx: {0}/{1} are unused", friendlyErrorNames, totalFriendlyErrorNames));
+
+ // Remove unused messages from Messages.rex. Note that this method is extremely
+ // crude and depends on the exact format of the XML.
+ if (removeUnused)
+ {
+ Console.WriteLine("Removing unused messages from Messages.resx");
+
+ List unusedFromMessages = new List();
+ foreach (string line in resources)
+ {
+ if (line.StartsWith("Messages."))
+ {
+ unusedFromMessages.Add(line.Substring(9));
+ }
+ }
+
+ string path = Path.Combine(rootDir, "Messages.resx");
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(File.ReadAllText(path));
+
+ List nodesToRemove = new List();
+ foreach (XmlNode node in doc.GetElementsByTagName("data"))
+ {
+ if (unusedFromMessages.Contains(node.Attributes["name"].Value))
+ {
+ nodesToRemove.Add(node);
+ }
+ }
+
+ foreach (XmlNode node in nodesToRemove)
+ {
+ doc.ChildNodes[1].RemoveChild(node);
+ }
+
+ doc.Save(path);
+ }
+ }
+
+ private static void RecursiveGetCsFiles(DirectoryInfo dir, List files)
+ {
+ files.AddRange(dir.GetFiles("*.cs"));
+ foreach (DirectoryInfo subdir in dir.GetDirectories())
+ {
+ RecursiveGetCsFiles(subdir, files);
+ }
+ }
+
+ private static void RecursiveGetResxFiles(DirectoryInfo dir, List files)
+ {
+ files.AddRange(dir.GetFiles("*.resx"));
+ foreach (DirectoryInfo subdir in dir.GetDirectories())
+ {
+ if (subdir.Name == "i18n")
+ continue;
+ RecursiveGetResxFiles(subdir, files);
+ }
+ }
+
+ private static void FindNodesInJaButNotEn(string rootDir)
+ {
+ // Find all english resxs
+ List enResxFiles = new List();
+ RecursiveGetResxFiles(new DirectoryInfo(rootDir), enResxFiles);
+
+ foreach (FileInfo enResxFile in enResxFiles)
+ {
+ string enResxPath = enResxFile.FullName;
+ XmlDocument enXml = new XmlDocument();
+ enXml.LoadXml(File.ReadAllText(enResxPath));
+
+ string jaFilename = enResxPath.Substring(rootDir.Length);
+ jaFilename = jaFilename.Insert(jaFilename.Length - 5, ".ja");
+ string jaResxPath = rootDir + "\\i18n\\ja" + jaFilename;
+ XmlDocument jaXml = new XmlDocument();
+ if (!File.Exists(jaResxPath))
+ {
+ continue;
+ }
+ jaXml.LoadXml(File.ReadAllText(jaResxPath));
+
+ XmlNodeList enDataNodes = enXml.GetElementsByTagName("data");
+ XmlNodeList jaDataNodes = jaXml.GetElementsByTagName("data");
+
+ List jaDataNodeList = new List();
+ foreach (XmlNode jaNode in jaDataNodes)
+ {
+ jaDataNodeList.Add(jaNode);
+ }
+
+ List inJaButNotEn = jaDataNodeList.FindAll((Predicate)delegate(XmlNode jaNode)
+ {
+ string jaDataName = jaNode.Attributes["name"].Value;
+ foreach (XmlNode enNode in enDataNodes)
+ {
+ if (enNode.Attributes["name"].Value == jaDataName)
+ {
+ return enNode.InnerXml != jaNode.InnerXml;
+ }
+ }
+ return true;
+ });
+
+ foreach (XmlNode node in inJaButNotEn)
+ {
+ System.Console.WriteLine(string.Format("'{0}' is in '{1}' but not in '{2}'",
+ node.Attributes["name"].Value,
+ jaResxPath,
+ enResxFile.Name));
+ }
+ }
+ }
+
+ private static readonly string[] i18nYes = new string[] { "Text", "ToolTipText", "HeaderText", "AccessibleDescription", "ToolTip", "Filter" };
+ private static readonly string[] i18nNo = new string[] { "ZOrder", "Size", "Location", "Anchor", "Type", "MinimumSize", "ClientSize",
+ "Font", "TabIndex", "Parent", "LayoutSettings", "Margin", "Padding", "ColumnCount", "Dock", "AutoSize", "Name", "ImeMode",
+ "IntegralHeight", "Visible", "InitialImage", "AutoScaleDimensions", "FlowDirection", "RowCount", "ImageAlign", "WrapContents",
+ "Enabled", "TextAlign", "StartPosition", "SizeMode", "Multiline", "ScrollBars", "ItemHeight", "CellBorderStyle", "AutoSizeMode",
+ "Image", "AutoCompleteCustomSource", "AutoCompleteCustomSource1", "AutoCompleteCustomSource2", "BulletIndent", "Width",
+ "MinimumWidth", "AutoScroll", "ImageSize", "MaxLength", "BackgroundImageLayout", "ImageTransparentColor", "ImageIndex",
+ "SplitterDistance", "MaximumSize", "ThousandsSeparator", "RightToLeft", "TextImageRelation", "ContentAlignment",
+ "SelectedImageIndex", "HorizontalScrollbar", "CheckAlign", "RightToLeftLayout", "ShowShortcutKeys", "ShortcutKeys",
+ "ShortcutKeyDisplayString", "Localizable", "Icon", "Menu", "AutoScrollMinSize", "Items", "ScrollAlwaysVisible",
+ "Items1", "Items2", "Items3", "MaxDropDownItems" };
+ private static bool IsI18nableProperty(string filename, string name)
+ {
+ foreach (string property in i18nYes)
+ {
+ if (name.EndsWith("." + property))
+ {
+ // Keep these tags
+ return true;
+ }
+ }
+
+ foreach (string property in i18nNo)
+ {
+ if (name.EndsWith("." + property))
+ {
+ // Reject these tags
+ return false;
+ }
+ }
+
+ // We haven't seen these tags before - keep them but issue a notification
+ Console.WriteLine(filename + ": " + name);
+ return true;
+ }
+
+ private static bool ExcludeResx(string filePath)
+ {
+ return filePath.EndsWith(@"\Properties\Resources.resx") ||
+ filePath.EndsWith(@"\Help\HelpManager.resx") ||
+ filePath.EndsWith(@"\DotNetVnc\KeyMap.resx");
+ }
+
+ ///
+ /// Try from the immediate window e.g.
+ /// XenAdmin.ResxCheck.TrimJaResxs(@"C:\Documents and Settings\hwarrington\xenadmin-unstable.hg\XenAdmin")
+ ///
+ ///
+ private static void TrimJaResxs(string rootDir)
+ {
+ // Find all english resxs
+ List enResxFiles = new List();
+ RecursiveGetResxFiles(new DirectoryInfo(rootDir), enResxFiles);
+
+ List names = new List();
+
+ foreach (FileInfo enResxFile in enResxFiles)
+ {
+ if (ExcludeResx(enResxFile.FullName))
+ {
+ continue;
+ }
+
+ // Load the en resx
+ string enResxPath = enResxFile.FullName;
+ XmlDocument enXml = new XmlDocument();
+ enXml.LoadXml(File.ReadAllText(enResxPath));
+ XmlNodeList enDataNodes = enXml.GetElementsByTagName("data");
+
+ // Find the ja resx
+ string jaFilename = enResxPath.Substring(rootDir.Length);
+ jaFilename = jaFilename.Insert(jaFilename.Length - 5, ".ja");
+ string jaResxPath = rootDir + "\\i18n\\ja" + jaFilename;
+
+ if (!File.Exists(jaResxPath))
+ {
+ // There is no ja resx file corresponding to the en resx. We need to check there are no i18nable tags
+ // in the en resx.
+ bool i18nRequired = false;
+ foreach (XmlNode enNode in enDataNodes)
+ {
+ if (IsI18nableProperty(enResxFile.Name, enNode.Attributes["name"].Value))
+ {
+ Console.WriteLine(string.Format("{0} is missing. Tag {1} needs i18n. Copying en resx across.", jaFilename, enNode.Attributes["name"].Value));
+ Directory.CreateDirectory(Path.GetDirectoryName(jaResxPath));
+ File.Copy(enResxPath, jaResxPath);
+ i18nRequired = true;
+ break;
+ }
+ }
+ if (!i18nRequired)
+ {
+ continue;
+ }
+ }
+
+ // Load the ja resx
+ XmlDocument jaXml = new XmlDocument();
+ jaXml.LoadXml(File.ReadAllText(jaResxPath));
+ XmlNodeList jaDataNodes = jaXml.GetElementsByTagName("data");
+ // Take a copy of the jaDataNodes
+ List jaDataNodeList = new List();
+ foreach (XmlNode node in jaDataNodes)
+ {
+ jaDataNodeList.Add(node);
+ }
+
+ // Go through all the ja nodes, keeping only the ones where their values differ from the en original
+ // Don't bother to do this for the messages files.
+ if (enResxFile.Name != "Messages.resx" && enResxFile.Name != "FriendlyNames.resx" &&
+ enResxFile.Name != "FriendlyNames.resx")
+ {
+ foreach (XmlNode jaNode in jaDataNodeList)
+ {
+ string jaDataName = jaNode.Attributes["name"].Value;
+
+ if (!IsI18nableProperty(jaFilename, jaDataName))
+ {
+ // Delete node
+ jaXml.GetElementsByTagName("root")[0].RemoveChild(jaNode);
+ continue;
+ }
+
+ foreach (XmlNode enNode in enDataNodes)
+ {
+ if (enNode.Attributes["name"].Value == jaDataName)
+ {
+ if (enNode.InnerXml == jaNode.InnerXml)
+ {
+ // If node unchanged, delete it
+ jaXml.GetElementsByTagName("root")[0].RemoveChild(jaNode);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Now add any nodes that are in en but not ja (as long as they are of the i18nable types).
+ foreach (XmlNode enNode in enDataNodes)
+ {
+ bool needToAdd = true;
+
+ foreach (XmlNode jaNode in jaDataNodes)
+ {
+ if (enNode.Attributes["name"].Value == jaNode.Attributes["name"].Value)
+ {
+ needToAdd = false;
+ break;
+ }
+ }
+
+ if (needToAdd && IsI18nableProperty(enResxFile.Name, enNode.Attributes["name"].Value))
+ {
+ XmlNode n = jaXml.GetElementsByTagName("root")[0].AppendChild(jaXml.ImportNode(enNode, true));
+ foreach (XmlNode child in n.ChildNodes)
+ {
+ if (child is XmlWhitespace)
+ continue;
+ if (child is XmlSignificantWhitespace)
+ continue;
+ else
+ child.InnerText += " (ja)";
+ }
+ }
+ }
+
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.CloseOutput = true;
+ settings.Indent = true;
+ XmlWriter writer = XmlWriter.Create(jaResxPath, settings);
+ jaXml.WriteContentTo(writer);
+ writer.Flush();
+ writer.Close();
+ }
+
+ Console.WriteLine("Done");
+ }
+
+ ///
+ /// Checks I haven't screwed stuff up while changing the autogen resx stuff.
+ ///
+ public static void CheckNotBorked()
+ {
+ XmlDocument origXml = new XmlDocument();
+ origXml.LoadXml(File.ReadAllText(@"C:\Documents and Settings\hwarrington\xenadmin-unstable.hg\XenAdmin\XenAPI\FriendlyNames.resx"));
+
+ XmlDocument newXml = new XmlDocument();
+ newXml.LoadXml(File.ReadAllText(@"Q:\local\scratch-2\hwarrington\build.hg\myrepos\api.hg\ocaml\idl\csharp_backend\autogen-gui\FriendlyNames.resx"));
+
+ CheckAIncludesB(origXml, newXml);
+ CheckAIncludesB(newXml, origXml);
+ }
+
+ private static XmlNode FindByName(XmlDocument doc, string name)
+ {
+ foreach (XmlNode node in doc.GetElementsByTagName("data"))
+ {
+ if (name == node.Attributes["name"].Value)
+ return node;
+ }
+ return null;
+ }
+
+ private static void CheckAIncludesB(XmlDocument origXml, XmlDocument newXml)
+ {
+ XmlNodeList origDataNodes = origXml.GetElementsByTagName("data");
+
+ foreach (XmlNode oldNode in origDataNodes)
+ {
+ string name = oldNode.Attributes["name"].Value;
+ string oldValue = oldNode.InnerXml;
+
+ XmlNode newNode = FindByName(newXml, name);
+ if (newNode == null)
+ {
+ throw new Exception(String.Format("Node with name {0} exists in old but not new!", name));
+ }
+ string newValue = newNode.InnerXml;
+ if (newValue != oldValue)
+ {
+ throw new Exception(String.Format("Node with name {0} has value {1} in old but {2} in new!", name, oldValue, newValue));
+ }
+ }
+ }
+ }
+}
diff --git a/devtools/ResxCheck/ResxCheck.csproj b/devtools/ResxCheck/ResxCheck.csproj
new file mode 100644
index 000000000..1cdaa01e7
--- /dev/null
+++ b/devtools/ResxCheck/ResxCheck.csproj
@@ -0,0 +1,60 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.30729
+ 2.0
+ {B61063FC-FCCB-4D4D-BA7B-CB0E674F1B16}
+ Exe
+ Properties
+ ResxCheck
+ ResxCheck
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+ 3.5
+
+
+ 3.5
+
+
+ 3.5
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/devtools/ResxCheck/ResxCheck.sln b/devtools/ResxCheck/ResxCheck.sln
new file mode 100644
index 000000000..869f156a6
--- /dev/null
+++ b/devtools/ResxCheck/ResxCheck.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResxCheck", "ResxCheck.csproj", "{B61063FC-FCCB-4D4D-BA7B-CB0E674F1B16}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B61063FC-FCCB-4D4D-BA7B-CB0E674F1B16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B61063FC-FCCB-4D4D-BA7B-CB0E674F1B16}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B61063FC-FCCB-4D4D-BA7B-CB0E674F1B16}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B61063FC-FCCB-4D4D-BA7B-CB0E674F1B16}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal