I needed the ability to load a XML file into a selectable list. The problem was really two fold, I had a collection of DAT files (like a single column CSV file) that I needed to first convert to XML (I had another existing collection of XML files, yea I know "consistency, consistency, consistency") as well.
So I can up with a simple couple of utility methods to convert a single column CSV file (herein referred to as a DAT file) to an XML and then another method to take a XML file and return a list collection. This allows me to first convert the DAT and then load the collection into a ComboBox.
At first I was worried about performance of reading from the XML files, but then I realized we are talking about a collection of strings around the order of 100. This loads using the XMLTextReader class in about 2/10 of a second. I think that is performance I can live with.
This method takes a plurized DAT file (much like the RoR MVC style) and creates a simple XML (that includes the root attributes that the partner XML read method expects). An example would be using a DAT file name ‘colors.dat’ it would make a XML file with ‘colors’ root node and then a collection of ‘color’ nodes with a single element named ‘name’ that contains the value of each DAT file.
Example DAT file (just for clarity):
red
yellow
orange
pink
blue
Method to convert the DAT to a XML file:
public void DATToXML(string DATFile, string XMLFile, bool SortList, bool IncludeEmpty, string IncludeAllCaption)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = (" ");
settings.Encoding = System.Text.Encoding.UTF8;
FileInfo datFileInfo = new FileInfo(DATFile);
string rootNode = datFileInfo.Name.Replace(datFileInfo.Extension.ToString(), "");
string childNode = rootNode.Substring(0, rootNode.Length - 1);
using (StreamReader sr = new StreamReader(DATFile))
{
string line = string.Empty;
using (XmlWriter writer = XmlWriter.Create(XMLFile, settings))
{
writer.WriteStartElement(rootNode);
writer.WriteAttributeString("displaymember", "name");
writer.WriteAttributeString("sortlist", SortList.ToString());
writer.WriteAttributeString("includeempty", IncludeEmpty.ToString());
writer.WriteAttributeString("includeall", IncludeAllCaption);
while ((line = sr.ReadLine()) != null)
{
if (!string.IsNullOrEmpty(line))
{
writer.WriteStartElement(childNode);
writer.WriteElementString("name", line);
writer.WriteEndElement();
}
}
writer.WriteEndElement();
writer.Flush();
}
}
}
Now the method to take the newly created XML file and load it into a List<string> collection:
public List<string> XMLToList(string XMLFile)
{
List<string> list = new List<string>();
XmlTextReader xml = new XmlTextReader(XMLFile);
string displayMember = string.Empty;
bool sortList = false;
string includeall = string.Empty;
bool includeempty = false;
while (xml.Read())
{
switch (xml.NodeType)
{
case XmlNodeType.Element:
while (xml.MoveToNextAttribute())
{
if (xml.Name == "displaymember")
displayMember = xml.Value.ToString();
if (xml.Name == "sortlist")
sortList = bool.Parse(xml.Value.ToString());
if (xml.Name == "includeall")
includeall = xml.Value.ToString();
if (xml.Name == "includeempty")
includeempty = bool.Parse(xml.Value.ToString());
}
while (xml.Read())
{
if (xml.Name.ToString() == displayMember)
{
// read next node, the textnode to get value
xml.Read();
if (xml.NodeType != XmlNodeType.Whitespace)
list.Add(xml.Value.ToString());
}
}
break;
}
}
// If includeall is set then put it at the first of the list
if (!string.IsNullOrEmpty(includeall))
list.Insert(0, includeall);
// If sort is set then sort the list before returning it from the method
if (sortList)
list.Sort(delegate(string s1, string s2) { return s1.CompareTo(s2); });
// if includeempty is true then put a empty string at the first of the list
if(includeempty)
list.Insert(0,string.Empty);
return list;
}
Bit of info on the options:
- The "displayMember" is the value of the element name to use for the string value (defaults to "name").
- The "sortList" option is a bool that indicates if the list should be sorted before it is returned.
- The "includeAll" option is a string value that will be put at the top of the list. Like "- All" or "* Select All *".
- The "includeEmpty" option is a bool that (if set "true") sets the option to include empty DAT file entries in the string collection.
So put together like this:
private void Form1_Load(object sender, EventArgs e)
{
string DATFile = @"C:\work\zipcodes.csv";
string XMLFile = @"C:\work\zipcodes.xml";
DATToXML(DATFile, XMLFile, true, false, string.Empty);
Stopwatch sw = new Stopwatch();
sw.Start();
List<string> zipcodes = XMLToList(XMLFile);
sw.Stop();
TimeSpan ts = sw.Elapsed;
this.labelXMLLoadTime.Text = "XML Load Time: " +
String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
sw.Start();
this.comboBox1.DataSource = zipcodes;
sw.Stop();
ts = sw.Elapsed;
this.labelComboboxLoadTime.Text = "Combobox Load Time: " +
String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
this.labelCount.Text = "Number Of Items Loaded: " + this.comboBox1.Items.Count.ToString("g");
}
Awesome stuff. Super simple and very fast. I loaded up the entire US Zipcode collection in .223 seconds and then put that into a ComboBox in 2.9 seconds. I realize most people are not going to have 40,000 entries in a selectable ComboBox, my point is that it is zooming fast.
Enjoy!