localization without re-compiling project - c#

I am building a project and I need to make it very customizable. I am trying to build it to support 4 languages. And the user will have an admin panel where he/she can change a label's text or a button's text. I want the user to go to that admin panel and change a button's text without calling me :)
I have used old but good style of localization which is .resx files. I have sample code below.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedItem.ToString().Equals("en-GB"))
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-GB");
label1.Text= FormLabels.test1;
label2.Text = FormLabels.test2;
}
else if (comboBox1.SelectedItem.ToString().Equals("de-DE"))
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
label1.Text = FormLabels.test1;
label2.Text = FormLabels.test2;
}
}
If I let user to change a button's text in "FormLabels.en-GB.resx" file. the project must be recompiled to see the changes.
I need to find a solution where the user can change the button's text with recompiling. how can I do that?

The only thing I can think about it to have the localization in external files.
Create a xml file like:
ex: languagesSupported.xml
<Languages>
<language name="English" file="en.dat" />
<language name="French" file="fr.dat" />
<language name="Japanese" file="jp.dat" />
</Languages>
Like this you can actually add more languages later on.
Now in each file you will need to do something like:
(ex: en.dat)
<Language name="English">
<Localized name="hello" value="Hello">
<Localized name="goodbye" value="Goodbye">
</Language>
(ex: fr.dat)
<Language name="French">
<Localized name="hello" value="Bonjour">
<Localized name="goodbye" value="Au revoir">
</Language>
In your code you would do something like that:
private Dictionary<string, Dictionary<string, string>> _localizations = new Dictionary<string, Dictionary<string, string>>();
private string _currentLocalization = "English";
private bool LoadLocalizations()
{
try
{
if (File.Exists("languagesSupported.xml") == false)
{
return false;
}
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load("languagesSupported.xml");
XmlNodeList nodeList = xmldoc.SelectNodes("languages/language");
if (nodeList.Count > 0)
{
foreach (XmlNode node in nodeList)
{
LoadLocalization(node.Attributes["name"].Value, node.Attributes["file"].Value);
}
}
return true;
}
catch (Exception ex)
{
return false;
}
}
private bool LoadLocalization(string pLang, string pFile)
{
try
{
if (File.Exists(pFile) == false)
{
return false;
}
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(pFile);
XmlNodeList nodeList = xmldoc.SelectNodes("language/localized");
_localizations.Add(pLang, new Dictionary<string,string>());
if (nodeList.Count > 0)
{
foreach (XmlNode node in nodeList)
{
_localizations[pLang].Add(node.Attributes["name"].Value, node.Attributes["value"].Value);
}
}
return true;
}
catch (Exception ex)
{
return false;
}
}
private void SetLocalization()
{
labelHello.text = _localizations[_currentLocalization]["hello"];
labelGoodbye.text = _localizations[_currentLocalization]["goodbye"];
}
After that, each time your user changes the language, you simply update _currentLocalization and call SetLocalization();
You can even populate your dropdownlist of language using the keys from _localizations.
That way you make the localization completely dynamic.
If you really want to use the CultureInfo, simply map the culture to the language name.

The ResourceManager only works with embeded resources.
You would have to write the ResourceManager yourself.
You could use the ResXResouceSet (http://msdn.microsoft.com/en-us/library/system.resources.resxresourceset.aspx) and then write your own ResourceManager around it.
It's not that hard to do.
You could then read the resx files and prepend the culture before the resx extension yourself.
Good luck

Related

How do I remove empty tags from xml file using c#

I have created a program in C# which adds and deletes data in an XML file.
Adding new data works totally fine, however on deleting the data, data gets deleted but empty tags still remain in the xml file.
How do I remove them? please help
The code is as follows:
private void deleteall_Click(object sender, EventArgs e) // delete single record button
{
XmlDocument xdata = new XmlDocument();
XmlNode xnode = xdata.SelectSingleNode("Information/Database");
xdata.Load("C:\\Users\\son14344\\Documents\\Visual Studio 2010\\Projects\\project.xml");
XmlNodeList oNodeList;
oNodeList = xdata.SelectNodes("Information/Database");
string s;
s = Convert.ToString(textBox1.Text);
try
{
foreach (XmlElement Oelement in oNodeList)
{
if (Oelement.SelectSingleNode("Database_Name").InnerText == s)
{
//Oelement.ParentNode.RemoveChild(Oelement);
Oelement.RemoveAll();
}
xdata.Save("C:\\Users\\son14344\\Documents\\Visual Studio 2010\\Projects\\project.xml");
//}
}
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
Try
Oelement.ParentNode.RemoveChild(Oelement);
instead of
Oelement.RemoveAll();
You have to capture the node that meets your condition.
Inside your if condition
XmlNode node = Oelement.SelectSingleNode("Database_Name");
XmlNode parent = node.ParentNode;
// remove the child node
parent.RemoveChild(node);

Add Text Information to Different XML Element each time information is submitted c#

I have an XML file that collects information with Button_Click, so it starts off empty.
XML Sample
<marina>
<dockone>
</dockone>
<docktwo>
</docktwo>
</marina>
When I submit information from a textbox, a new XmlNode is created called slipone, and another XmlNode called reg is nested within that.
XML Sample 2
<marina>
<dockone>
<slipone>
<reg>12345</reg>
<slipone>
</dockone>
<docktwo>
</docktwo>
</marina>
I have attempted to create an if/else statement that will add a new XmlNode called sliptwo, with reg still nested within it, if slipone already has text, like so:
<marina>
<dockone>
<slipone>
<reg>12345</reg>
<slipone>
<sliptwo>
<reg>67890</reg>
<sliptwo>
</dockone>
<docktwo>
</docktwo>
</marina>
However the closest I have gotten is another XMlnode is still created, however it labels itself as slipone, and I am not sure what I am doing wrong:
<marina>
<dockone>
<slipone>
<reg>12345</reg>
<slipone>
<slipone>
<reg>67890</reg>
<slipone>
</dockone>
<docktwo>
</docktwo>
</marina>
This is an example of what I have been playing around with. Ignore the operators as I have resorted to trial and error but still have gotten nowhere. Please help!
C# Example
XmlDocument XmlDocObj1 = new XmlDocument();
XmlDocObj1.Load(Server.MapPath("~/App_Data/SlipData.xml"));
XmlNode rootnode1 = XmlDocObj1.SelectSingleNode("marina/dockone");
XmlNode dockone = rootnode1.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "slipone", ""));
XmlNode docktwo = rootnode1.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "sliptwo", ""));
XmlNode dockthree = rootnode1.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "slipthree", ""));
if (regfinal.Text != dockone.InnerText)
{
dockone.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "Reg", "")).InnerText = regfinal.Text;
XmlDocObj1.Save(Server.MapPath("/App_Data/SlipData.xml"));
}
else if (regfinal.Text == dockone.InnerText)
{
docktwo.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "Reg", "")).InnerText = regfinal.Text;
XmlDocObj1.Save(Server.MapPath("/App_Data/SlipData.xml"));
}
Your logic isn't going to do what I think you are saying since the only time (regfinal.Text != dockone.InnerText) will evaluate to false is when you enter nothing in your text control.
I believe you might mean to say if dockone exists then create another node called docktwo. This will require you to change your logic.
Some very simple code to get you a bit farther down the path. Not intended to be perfect or solve all problems...
private void button1_Click(object sender, EventArgs e)
{
XmlDocument XmlDocObj1 = new XmlDocument();
XmlDocObj1.Load(AppDomain.CurrentDomain.BaseDirectory.ToString()+"test.xml");
XmlNode rootnode1 = XmlDocObj1.SelectSingleNode("marina/dockone");
XmlNode dockone = rootnode1.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "slipone", ""));
XmlNode docktwo = rootnode1.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "sliptwo", ""));
XmlNode dockthree = rootnode1.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "slipthree", ""));
//jsh: old logic
//if (textBox1.Text != dockone.InnerText)
//new logic to test whether we have already created the dockone node which should only occur once
//you already have the logic for selecting the dockone node above...now just test if you already have it.
//NOTE: you may actually want a switch statement given that you avhe dockone, docktwo, and dockthree or at least another
// if statement to see if docktwo has been created and thus creaste dockthree.
if (rootnode1 == null )
{
dockone.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "Reg", "")).InnerText = textBox1.Text;
XmlDocObj1.Save(AppDomain.CurrentDomain.BaseDirectory.ToString() + "test.xml");
}
//else if (textBox1.Text == dockone.InnerText) jsh: old logic
else
{
docktwo.AppendChild(XmlDocObj1.CreateNode(XmlNodeType.Element, "Reg", "")).InnerText = textBox1.Text;
XmlDocObj1.Save(AppDomain.CurrentDomain.BaseDirectory.ToString() + "test.xml");
}
}

Creating an xml parsing function in C#

I have an XML that's obtained from a web service, i'm using an HttpClient for it. This is what the XML looks like:
<respuesta>
<entrada>
<rut>7059099</rut>
<dv>9</dv>
</entrada>
<status>
<code>OK</code>
<descrip>Persona tiene ficha, ok</descrip>
</status>
<ficha>
<folio>3204525</folio>
<ptje>7714</ptje>
<fec_aplic>20080714</fec_aplic>
<num_integ>2</num_integ>
<comuna>08205</comuna>
<parentesco>1</parentesco>
<fec_puntaje>20070101</fec_puntaje>
<personas>
<persona>
<run>7059099</run>
<dv>9</dv>
<nombres>JOSE SANTOS</nombres>
<ape1>ONATE</ape1>
<ape2>FERNANDEZ</ape2>
<fec_nac>19521101</fec_nac>
<sexo>M</sexo>
<parentesco>1</parentesco>
</persona>
<persona>
<run>8353907</run>
<dv>0</dv>
<nombres>JUANA DEL TRANSITO</nombres>
<ape1>MEDINA</ape1>
<ape2>ROA</ape2>
<fec_nac>19560815</fec_nac>
<sexo>F</sexo>
<parentesco>2</parentesco>
</persona>
</personas>
</ficha>
I'm trying to make a function that can parse this and, right now (just for the purpose of testing my understanding of the language since i'm new to it) i just need it to find the VALUE inside an "rut" tag, the first one, or something like that. More precisely I need to find a value inside the XML and return it, so i can show it on a label that's on my .aspx page. The code of my parsing function looks like this:
public static String parseXml(String xmlStr, String tag)
{
String valor;
using (XmlReader r = XmlReader.Create(new StringReader(xmlStr)))
{
try
{
r.ReadToFollowing(tag);
r.MoveToContent();
valor = r.Value;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex.InnerException);
}
}
return valor;
}
This code is based on an example I found on youtube made by the guys from microsoft where they "explain" how to use the parser.
Also, this function is being called from inside one of the tasks of the HttpClient, this is it:
protected void rutBTN_Click(object sender, EventArgs e)
{
if (rutTB.Text != "")
{
HttpClient client = new HttpClient();
String xmlString = "";
String text = "";
var byteArray = Encoding.ASCII.GetBytes("*******:*******"); //WebService's server authentication
client.BaseAddress = new Uri("http://wschsol.mideplan.cl");
var par = "mod_perl/xml/fps-by-rut?rut=" + rutTB.Text;
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
client.GetAsync(par).ContinueWith(
(requestTask) =>
{
HttpResponseMessage resp = requestTask.Result;
try
{
resp.EnsureSuccessStatusCode();
XmlDocument xmlResp = new XmlDocument();
requestTask.Result.Content.ReadAsStreamAsync().ContinueWith(
(streamTask) =>
{
xmlResp.Load(streamTask.Result);
text = xmlResp.InnerXml.ToString();
xmlString = parseXml(text, "rut"); //HERE I'm calling the parsing function, and i'm passing the whole innerXml to it, and the string "rut", so it searches for this tag.
Console.WriteLine("BP");
}).Wait();
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex.InnerException);
}
}).Wait();
testLBL.Text = xmlString; //Finally THIS is the label i want to show the "rut" tag's value to be shown.
testLBL.Visible = true;
}
else
{
testLBL.Text = "You must enter an RUT number";
testLBL.Visible = true;
}
}
The problem is that when i put some breakpoints into the parsing function i can see that it's receiving correctly the innerxml string (as a string) but it's not finding the tag called "rut", or rather not finding anything at all, since it's returning an empty string ("").
I know that maybe this is not the correct way to parse an xmlDocument, so if someone can help me out i'd be really really thankful.
EDIT:
Ok, so i won't ask for any tutorial or such (I requested that to avoid asking noob questions). But anyway, please, instead of just answering "you better do it like this", I'd appreciate if you could explain me things like "THIS is what you're doing wrong and THAT'S why your code isn't working", and THEN tell me how you guys would do it instead.
Thanks in advance!
As you only want to retrieve a single field value I would recommend using Xpath.
Basically you create a XpathNavigator from a XpathDocument or xmlDocument and then use Select to get the content of the rut node:
XPathNavigator navigator = xmlResp.CreateNavigator();
XPathNodeIterator rutNode = navigator.SelectSingleNode("/respuesta/entrada/rut");
string rut = rutNode.Value

Reading XML node from C# when it's a resource in XAML

I'm having some trouble with the following. I'm a beginner, which is probably why.
I have a listbox that displays some pictures, it gets the paths of these pictures from an XML file. This XML file is defined as a resource in XAML. If a picture is selected and the user presses enter, I want to launch an external app with some parameters, including a path found in another node of that XML file (appath in the example below).
XML layout:
<picture>
<path></path>
<appath></appath>
</picture>
I can't seem to find the way to access the node from C#.
Any pointers greatly appreciated!
Thanks in advance,
J.
If you don't have any attributes in the picture node (an id of some sort) you, have to first match up on the path which you should already have in your listbox, then return the appath.
static string GetAppath(string xmlString, string picPath)
{
string appath = String.Empty;
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(xmlString);
XmlNodeList xList = xDoc.SelectNodes("/picture");
foreach (XmlNode xNode in xList)
{
if (xNode["path"].InnerText == picPath)
{
appath = xNode["appath"].InnerText;
break;
}
}
return appath;
}
Assuming your xml file looks something like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<pictures>
<picture>
<path></path>
<appath></appath>
</picture>
</pictures>
If your resource name is Pictures:
XElement resource = XElement.Parse(Properties.Resources.Pictures);
Using these extensions: (just copy the class/file into your root directory of your project) http://searisen.com/xmllib/extensions.wiki
public class PicturesResource
{
XElement self;
public PicturesResource()
{ self = XElement.Parse(Properties.Resources.Pictures); }
public IEnumerable<Picture> Pictures
{ get { return self.GetEnumerable("picture", x => new Picture(x)); } }
}
public class Picture
{
XElement self;
public Pictures(XElement self) { this.self = self; }
public string Path { get { return self.Get("path", string.Empty); } }
public string AppPath { get { return self.Get("apppath", string.Empty); } }
}
You could then bind the Pictures or do a look up on them:
PicturesResource pictures = new PicturesResource();
foreach(Picture pic in pictures.Pictures)
{
string path = pic.Path;
string apppath = pic.AppPath;
}
Or searching for a particular picture:
Picture pic = pictures.FirstOrDefault(p => p.Path = "some path");
if(pic != null)
{
// do something with pic
}

Can ConfigurationManager retain XML comments on Save()?

I've written a small utility that allows me to change a simple AppSetting for another application's App.config file, and then save the changes:
//save a backup copy first.
var cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
cfg.SaveAs(cfg.FilePath + "." + DateTime.Now.ToFileTime() + ".bak");
//reopen the original config again and update it.
cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
var setting = cfg.AppSettings.Settings[keyName];
setting.Value = newValue;
//save the changed configuration.
cfg.Save(ConfigurationSaveMode.Full);
This works well, except for one side effect. The newly saved .config file loses all the original XML comments, but only within the AppSettings area. Is it possible to to retain XML comments from the original configuration file AppSettings area?
Here's a pastebin of the full source if you'd like to quickly compile and run it.
I jumped into Reflector.Net and looked at the decompiled source for this class. The short answer is no, it will not retain the comments. The way Microsoft wrote the class is to generate an XML document from the properties on the configuration class. Since the comments don't show up in the configuration class, they don't make it back into the XML.
And what makes this worse is that Microsoft sealed all of these classes so you can't derive a new class and insert your own implementation. Your only option is to move the comments outside of the AppSettings section or use XmlDocument or XDocument classes to parse the config files instead.
Sorry. This is an edge case that Microsoft just didn't plan for.
Here is a sample function that you could use to save the comments. It allows you to edit one key/value pair at a time. I've also added some stuff to format the file nicely based on the way I commonly use the files (You could easily remove that if you want). I hope this might help someone else in the future.
public static bool setConfigValue(Configuration config, string key, string val, out string errorMsg) {
try {
errorMsg = null;
string filename = config.FilePath;
//Load the config file as an XDocument
XDocument document = XDocument.Load(filename, LoadOptions.PreserveWhitespace);
if(document.Root == null) {
errorMsg = "Document was null for XDocument load.";
return false;
}
XElement appSettings = document.Root.Element("appSettings");
if(appSettings == null) {
appSettings = new XElement("appSettings");
document.Root.Add(appSettings);
}
XElement appSetting = appSettings.Elements("add").FirstOrDefault(x => x.Attribute("key").Value == key);
if (appSetting == null) {
//Create the new appSetting
appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", val)));
}
else {
//Update the current appSetting
appSetting.Attribute("value").Value = val;
}
//Format the appSetting section
XNode lastElement = null;
foreach(var elm in appSettings.DescendantNodes()) {
if(elm.NodeType == System.Xml.XmlNodeType.Text) {
if(lastElement?.NodeType == System.Xml.XmlNodeType.Element && elm.NextNode?.NodeType == System.Xml.XmlNodeType.Comment) {
//Any time the last node was an element and the next is a comment add two new lines.
((XText)elm).Value = "\n\n\t\t";
}
else {
((XText)elm).Value = "\n\t\t";
}
}
lastElement = elm;
}
//Make sure the end tag for appSettings is on a new line.
var lastNode = appSettings.DescendantNodes().Last();
if (lastNode.NodeType == System.Xml.XmlNodeType.Text) {
((XText)lastNode).Value = "\n\t";
}
else {
appSettings.Add(new XText("\n\t"));
}
//Save the changes to the config file.
document.Save(filename, SaveOptions.DisableFormatting);
return true;
}
catch (Exception ex) {
errorMsg = "There was an exception while trying to update the config value for '" + key + "' with value '" + val + "' : " + ex.ToString();
return false;
}
}
If comments are critical, it might just be that your only option is to read & save the file manually (via XmlDocument or the new Linq-related API). If however those comments are not critical, I would either let them go or maybe consider embedding them as (albeit redundant) data elements.

Categories