- Same Old Data
- The Basic Edit Example
- Putting It Together
- Example Web Interface
Putting It Together
Remember the namespace that we created? If not, then you need not worry, because it still exists. This is mostly for reference purposes, but in Listing 2 you will find a new and improved namespace that incorporates editing functionality. Copy and paste that into a file named contacts_lib.cs under the Web site's /bin directory.
Listing 2: Test Namespace with Editing Functionality
namespace test { using System; using System.Collections; using System.Xml; using System.Xml.XPath; using System.Web; public class Contact { public Contact() { szFirstName = String.Empty; szLastName = String.Empty; szMiddleName = String.Empty; szGender = String.Empty; szInitials = String.Empty; szKeywords = String.Empty; oAddresses = new Hashtable(); oPhones = new Hashtable(); } public Contact(XPathNavigator oNav, String szId) { // Grab the contact info //and initialize the data members. String szQry = "personal_information/[ccc] contacts/entry[@id='" + szId + "']"; XPathNodeIterator oIterator = oNav.Select(szQry); oIterator.MoveNext(); XPathNodeIterator oName = oIterator.Current.Select("./name"); oName.MoveNext(); szFirstName = GetAttributeText(oName.Current, "first"); szMiddleName = GetAttributeText(oName.Current, "middle"); szLastName = GetAttributeText(oName.Current, "last"); szInitials = String.Empty; szKeywords = String.Empty; if (GetAttributeText(oName.Current, "gender") == "f") { szGender = "Female"; } else { szGender = "Male"; } oAddresses = new Hashtable(); Address oHome = new Address(oIterator.Current, "home"); Address oWork = new Address(oIterator.Current, "work"); oAddresses.Add("home", oHome); oAddresses.Add("work", oWork); oPhones = new Hashtable(); PhoneNumber oPH = new PhoneNumber(oIterator.Current, "home"); PhoneNumber oPW = new PhoneNumber(oIterator.Current, "work"); oPhones.Add("home", oPH); oPhones.Add("work", oPW); } public PhoneNumber GetPhoneNumber(String szType) { return (PhoneNumber) oPhones[szType]; } public Address GetAddress(String szType) { return (Address) oAddresses[szType]; } private String GetAttributeText(XPathNavigator nav, [ccc] String szName) { String szRetval = String.Empty; nav.MoveToAttribute(szName, ""); szRetval = nav.Value; nav.MoveToParent(); return szRetval; } public String szFirstName; public String szMiddleName; public String szLastName; public String szGender; public String szKeywords; public String szInitials; private Hashtable oAddresses; private Hashtable oPhones; } public class Address { public Address() { szState = String.Empty; szCity = String.Empty; szZip = String.Empty; } public Address(XPathNavigator oNav, String szType) { try { szState = String.Empty; szCity = String.Empty; szZip = String.Empty; XPathExpression oExpr; String szExpr = String.Empty; szExpr = "./address[@type='" + szType + "']/line"; oExpr = oNav.Compile(szExpr); ContactCompare oAddrCompare = new ContactCompare(); // Add a comparer to do a string compare oExpr.AddSort("@seq", (IComparer)oAddrCompare); XPathNodeIterator oIterator = oNav.Select(oExpr); int x = 0; aLines = new String[oIterator.Count]; while (oIterator.MoveNext()) { aLines[x] = oIterator.Current.Value; x++; } szExpr = "./address[@type='" + szType + "']/city"; oIterator = oNav.Select(szExpr); oIterator.MoveNext(); szCity = oIterator.Current.Value; szExpr = "./address[@type='" + [ccc] szType + "']/state"; oIterator = oNav.Select(szExpr); oIterator.MoveNext(); szState = oIterator.Current.Value; szExpr = "./address[@type='" + [ccc] szType + "']/zipcode"; oIterator = oNav.Select(szExpr); oIterator.MoveNext(); szZip = oIterator.Current.Value; oIterator.Current.MoveToParent(); } catch (ArgumentException ex) { String blah = ex.Message; szState = String.Empty; szCity = String.Empty; szZip = String.Empty; } } public String[] aLines; public String szState; public String szCity; public String szZip; } class ContactCompare : IComparer { public int Compare(Object First, Object Second) { String s1 = (String) First; String s2 = (String) Second; return s1.ToString().CompareTo(s2.ToString()); } } public class PhoneNumber { public PhoneNumber(String szAc, String szNum) { szACode = szAc; szNumber = szNum; } public PhoneNumber(XPathNavigator oNav, String szType) { String szExpr = String.Empty; szExpr = "./phone[@type='" + szType + "']"; XPathNodeIterator oIterator = oNav.Select(szExpr); oIterator.MoveNext(); szACode = GetAttributeText(oIterator.Current, "acode"); szNumber = GetAttributeText(oIterator.Current, "number"); } private String GetAttributeText(XPathNavigator nav, [ccc] String szName) { String szRetval = String.Empty; nav.MoveToAttribute(szName, ""); szRetval = nav.Value; nav.MoveToParent(); return szRetval; } public String szACode; public String szNumber; } // Only manages one contacts xml file at a time. public class ContactManager { public ContactManager(String szFile, TraceContext Trc) { Trace = Trc; szFilePath = szFile; // Initialize the XPath classes. oDoc = new XmlDocument(); LoadData(); } public bool AddContact(Contact oSave) { try { // Currently only adds these nodes. //<entry id="0" initials="jxd"> //<name first="John" middle="X." last="Doe" gender="m" /> //</entry> String szQry = "personal_information/contacts"; XmlNode oRoot = oDoc.SelectSingleNode(szQry); XmlNode oEntry = oDoc.CreateElement("entry"); XmlAttribute oId = oDoc.CreateAttribute("id"); XmlAttribute oInit = oDoc.CreateAttribute("initials"); oId.Value = Convert.ToString(iContactCtr); oInit.Value = oSave.szInitials; oEntry.Attributes.Append(oId); oEntry.Attributes.Append(oInit); XmlNode oName = oDoc.CreateElement("name"); XmlAttribute oFirst = oDoc.CreateAttribute("first"); XmlAttribute oMid = oDoc.CreateAttribute("middle"); XmlAttribute oLast = oDoc.CreateAttribute("last"); XmlAttribute oGndr = oDoc.CreateAttribute("gender"); oFirst.Value = oSave.szFirstName; oMid.Value = oSave.szMiddleName; oLast.Value = oSave.szLastName; oGndr.Value = oSave.szGender; oName.Attributes.Append(oFirst); oName.Attributes.Append(oMid); oName.Attributes.Append(oLast); oName.Attributes.Append(oGndr); oRoot.AppendChild(oEntry); String szCtr = Convert.ToString(iContactCtr); Trace.Write("AddContact", szCtr); Flush(); Trace.Write("AddContact", "Contact added"); } catch (Exception ex) { String blah = ex.Message; Trace.Write("AddContact", blah); return false; } return true; } public bool DeleteContact(String szId) { String szQry = "personal_information/contacts/[ccc] entry[@id=\""+ szId + "\"]"; XmlNode oDelete = oDoc.SelectSingleNode(szQry); if (oDelete != null) { try { String szCnQry = "personal_information/contacts"; XmlNode oContacts = oDoc.SelectSingleNode(szCnQry); oContacts.RemoveChild(oDelete); // Renumber the ids to avoid collisions. String sQy = "personal_information/contacts/" +[ccc] "entry"; XmlNodeList oNodes = oDoc.SelectNodes(sQyy); if (oNodes != null) { Trace.Write("DeleteContact","oNodes.length [ccc] = " + Convert.ToString(oNodes.Count)); int x = 0; foreach(XmlNode oNode in oNodes) { String szNew = Convert.ToString(x); oNode.Attributes["id"].Value = szNew; x++; } } else { Trace.Write("DeleteContact", "oNodes is [ccc] null. can't renumber the entries"); } } catch (Exception ex) { String blah = ex.Message; Trace.Write("DeleteContact", blah); return false; } return true; } else { return false; } } public Contact GetContact(String szId) { Contact pTemp = new Contact(oNav, szId); return pTemp; } public Contact GetContact(int Id) { Contact pTemp = new Contact(oNav, Convert.ToString(Id)); return pTemp; } public int Count() { return iContactCtr; } public bool Flush() { oDoc.Save(szFilePath); LoadData(); return true; } private void LoadData() { oDoc.Load(szFilePath); oNav = oDoc.CreateNavigator(); oNav.MoveToRoot(); // Get a count of all entries. String szQry = "personal_information/contacts/entry"; XPathNodeIterator oIterator = oNav.Select(szQry); iContactCtr = oIterator.Count; } private XPathNavigator oNav; private XmlDocument oDoc; private int iContactCtr; private String szFilePath; private TraceContext Trace; } }
Looking closer at the DeleteContact member, we see how easy it is to complete a delete for this XML. The ID attribute from the entry node is used to communicate which node to delete. Then it is selected, and RemoveChild is called from an XmlNodeList object that contains the specified node. The only thing that is missing is the actual save to disk. Each time delete is called, it only updates the current XML in memory for the specified instance of ContactManager.
NOTE
A Flush() member was provided in the namespace in Listing 2 so that UI code could save after completing all deletes.
Deleting nodes is very easy, but one thing is still missing from your arsenal of XML editing knowledge. The ability to add data is just as important as deleting it. Without it, we could never reinsert long-lost friends or acquaintances back into our data, if required. Adding nodes to XML requires a bit more work than deleting them on our part. The basic method calls used are from a number of classes (System.Xml namespace), as follows:
- CreateElement()
- CreateAttribute()
- set property Value
- Attributes.Append()
- AppendChild()
(Repeat process as required.)
The only thing that has not been done in AddContact is to set the node value itself. That task can be accomplished by setting the value of the node returned from CreateElement. With the ContactManager class, a slightly different approach must be taken toward adds so that the XML business is abstracted away from the page UI. In this case, we utilize the Contact data instance class to provide a method to communicate the data to the ContactManager class method AddContact. Once all the proper nodes are created and appended properly, we then use the Flush() method to save the DOM to disk. Of importance is that this differs from DeleteContact because only one contact can be added at a time. This work is monotonous and best abstracted from the UI, although the UI has a bit of work ahead of it to represent this data in HTML to the user. Let's go on to a small example to help illustrate the usage of these new namespace features.