0

I'm trying to learn to use xml files in my application. I want to do a simple read of an element, and optionally rewrite an element and get its value. I have an xml file that looks something like:

<?xml version="1.0" encoding="utf-16"?>
<PTH>
  <Account>
    <Username>aa</Username>
    <Password>xx</Password>
    <Email>xyz@xyz.com</Email>
    <Role>Student</Role>
  </Account>
  <ChartName>
    <Id>1</Id>
    <Name>John Smith</Name>
    <PlaceOfBirth>louisville, ky</PlaceOfBirth>
    <BirthDate>1/1/70</BirthDate>
    <TimeZone>Greenwich</TimeZone>
  </ChartName>
  <ChartName>
    <Id>2</Id>
    <Name>John Smith</Name>
    <PlaceOfBirth>New York, NY</PlaceOfBirth>
    <BirthDate>1/1/1980</BirthDate>
    <TimeZone>Greenwich</TimeZone>
  </ChartName>
  <ChartName>
    <Id>3</Id>
    <Name>Jane Doe</Name>
    <PlaceOfBirth>Los Angeles, Ca</PlaceOfBirth>
    <BirthDate>1/1/1990</BirthDate>
    <TimeZone>Greenwich</TimeZone>
  </ChartName>
</PTH>

Where there will only be one Account element and multiple ChartName elements. The code I came up with is:

public class Account
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string Email { get; set; }
    public string Role { get; set; }
}

...

    XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath("~") + "\\App_Data\\" + username.Text.Trim());
    XElement xEle = xml.Element("PTH");

    var Accounts = (from Account in xml.Root.Elements("Account")
                   select new
                   {
                       Email = (string)Account.Element("Email").Value,
                       Role = (string)Account.Element("Role").Value
                   });
    foreach (var Account in Accounts)
    {
        Session["Email"] = Account.Email;
        Session["Role"] = Account.Role;
    }

The code works, but seems to be a really, REALLY convoluted way just to read the values of an element. Also, I'm not quite sure how I can, say, rewrite just the email element value. Does anyone have a much simple way to read and/or rewrite an element value? It seems like it should be very basic, but it escapes me...

SteveFerg
  • 3,466
  • 7
  • 19
  • 31
  • if there is only 1 `Account` node, why the `foreach` and Linq? I think you can use XPath and just select the single nodes to get their values (InnerText). [Reference](https://msdn.microsoft.com/en-us/library/h0hw012b(v=vs.110).aspx) – zgood Jun 27 '16 at 21:02
  • Yeah, the 'foreach' seems like overkill, but that is what I found in the tutorials I had been reading. Most tutorials show simple xml files with only one type of main elements like 'book' for example and not files with multiple element types. – SteveFerg Jun 27 '16 at 21:11
  • Do you have control over the XML format? If the elements were wrapped in a parent, it would be serializable. – Derpy Jun 27 '16 at 21:23
  • Yes I do have control over it, and I'm open, but I also want to have other element types within the file. I might later want to add a 'Preferences' element. I am basically coming from the mentality that for years I have used ReadPrivateProfileString and WritePrivateProfileString and want to get away from that archaic way of using ini files. – SteveFerg Jun 27 '16 at 21:28
  • @SteveFerg In that case, you might also thingk about using [JSON](http://www.newtonsoft.com/json) – Sam I am says Reinstate Monica Jun 27 '16 at 21:30
  • I considered JSON, but once I get comfortable with xml, there will be some considerable cpu intensive processing on the server side with the data within the file, and ver little, if any, on the client side. I like your answer below, and it gives great explanations and things for me to further research. – SteveFerg Jun 27 '16 at 21:46

2 Answers2

1

I've written out this sample for you. It's only 6 lines, and it reads from and writes to an XML document.

I've included comments explaining what's going on.

        // this loads up your XDocument.  your XDocument is an object which represents your xml file.
        XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath("~") + "\\App_Data\\" + username.Text.Trim());

        // this is a quick and dirty way to get the all of the elements element named "Email"
        // don't worry too much about "var" it's still strongly typed, and is just a syntatical shortcut so I dont' have to actually type out 
        // the name of whatever type xml.Descendant returns.
        var emails = xml.Descendants("Email");

        // if you want just one, use "First"
        var firstEmail = emails.First();

        // if you want the value, use the ".Value" property
        Console.WriteLine(firstEmail.Value);

        // if you want to change it, use normal assignment, and
        firstEmail.Value = "JohnDoe@gmail.com";

        // if you want to persist your changes to disk, save your document.
        xml.Save("C:\\temp\\otherfolder\\xmldocument.xml");

If you want even more shorthands, you can look at This answer about how to use XPath syntax (the answer is slightly obsolete, that the namespace you now use is using System.Xml.XPath;

Community
  • 1
  • 1
1

If you have control over the XML format, I find serialization to be a more civilized approach to parsing XML. JSON is actually my format of choice, but XML works too. If you want to add a preferences property later, you can just add it to your PTH class.

    private void Test()
    {
        var obj = new PTH()
        {
            Account = new Account() { UserName = "bob's", Password = "burgers" },
            ChartNames = Enumerable.Range(1, 3).Select(x => new ChartName() { Id = x, name = "Name_" + x.ToString() }).ToArray()
        };
        var xml = SerializeXML(obj);
        var objDeserialized = DeserializeXML<PTH>(xml);
        var chartsToChange = objDeserialized.ChartNames.Where(x => x.Id == 1).ToList();
        foreach (var chart in chartsToChange)
        {
            chart.name = "new name";
        }

        var backToXML = SerializeXML(objDeserialized);
    }

    public static string SerializeXML<T>(T obj)
    {
        var izer = new System.Xml.Serialization.XmlSerializer(typeof(T));

        using (var stringWriter = new StringWriter())
        {
            using (var xmlWriter = new XmlTextWriter(stringWriter))
            {
                izer.Serialize(xmlWriter, obj);
                return stringWriter.ToString();
            }
        }
    }

    public static T DeserializeXML<T>(string xml)
    {
        var izer = new System.Xml.Serialization.XmlSerializer(typeof(T));

        using (var stringReader = new StringReader(xml))
        {
            using (var xmlReader = new XmlTextReader(stringReader))
            {
                return (T)izer.Deserialize(xmlReader);
            }
        }
    }
    public class PTH
    {
        public Account Account { get; set; }
        public ChartName[] ChartNames { get; set; }
    }
    public class Account
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }

    public class ChartName
    {
        public int Id { get; set; }
        public string name { get; set; }
    }

The XML is not much different, just that the array items need a parent node.

<?xml version="1.0" encoding="UTF-8"?>
<PTH xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <Account>
      <UserName>bob's</UserName>
      <Password>burgers</Password>
   </Account>
   <ChartNames>
      <ChartName>
         <Id>1</Id>
         <name>Name_1</name>
      </ChartName>
      <ChartName>
         <Id>2</Id>
         <name>Name_2</name>
      </ChartName>
      <ChartName>
         <Id>3</Id>
         <name>Name_3</name>
      </ChartName>
   </ChartNames>
</PTH>
Derpy
  • 1,458
  • 11
  • 15
  • This gives me another way to look at it. When I start looking at the astrological charts, most reading/writing will be in the chart names. I'm looking at writing the utility functions needed for processing, so this solution is quite helpful. Thank-you. – SteveFerg Jun 28 '16 at 18:17