How to save and get values from an Xml without cast?

293 views Asked by At

Something which I really hate is to cast each element or attribute value from a Xml file.

This momment, I'm creating in hundreds of modules a methods where specifies how to convert an object into a XmlFile. Believe, this is very tired. So I'm thinking in an alternative.

I was investigating about XSD, I'm not sure if this will be my salvation. I'm using Linq to Xml to save and get the values. I mean, my objects are composed like this:

- Foo1 : Foo
   - Range1 : Range
      - X : int
      - Y : int
- ...

As you can see, they have many nodes. Is there another alternative to do this? I mean, strongly types.

4

There are 4 answers

0
Chuck Savage On BEST ANSWER

You can try these XElement extension methods: http://searisen.com/xmllib/extensions.wiki

Here is an example of the power of it, given this xml from another post:

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <PatchCreation
    Id="224C316C-5894-4771-BABF-21A3AC1F75FF"
    CleanWorkingFolder="yes"
    OutputPath="patch.pcp"
    WholeFilesOnly="yes">
    <PatchInformation
        Description="Update Patch"
        Comments="Update Patch"
        ShortNames="no"
        Languages="1033"
        Compressed="yes"
        Manufacturer="me"/>

    <PatchMetadata
        AllowRemoval="yes"
        Description="Update Patch"
        ManufacturerName="me"
        TargetProductName="Update"
        MoreInfoURL="http://andrewherrick.com/"
        Classification="Update"
        DisplayName="Update Patch"/>

    <Family DiskId="5000"
        MediaSrcProp="Sample"
        Name="Update"
        SequenceStart="5000">
      <UpgradeImage SourceFile="c:\new.msi" Id="PatchUpgrade">
        <TargetImage SourceFile="c:\old.msi" Order="2" Id="PatchUpgrade" IgnoreMissingFiles="no" />
      </UpgradeImage>
    </Family>

    <PatchSequence PatchFamily="SamplePatchFamily"
        Sequence="1.0.0.0"
        Supersede="yes" />
  </PatchCreation>
</Wix>

This sets the value of the UpgradeImage tag's SourceFile Attribute and the TargetImage tag inside the UpgradeImage and its SourceFile.

XElement wix = XElement.Load(xmlFile1.FullName);
wix.Set("PatchCreation/Family/UpgradeImage/SourceFile", "upgrade path", true)
   .Set("TargetImage/SourceFile", "target path", true);

You can also get their values in the same fashion (no casts).

string upgradeSource = wix.Get("PatchCreation/Family/UpgradeImage/SourceFile", string.Empty);
string targetSource = wix.Get("PatchCreation/Family/UpgradeImage/TargetImage/SourceFile", string.Empty);

Or this can be written as:

XElement upgradeImage = wix.GetElement("PatchCreation/Family/UpgradeImage");
string upgradeSource = upgradeImage.Get("SourceFile", string.Empty);
string targetSource = upgradeImage.Get("TargetImage/SourceFile", string.Empty);

To get a list of integers:

<root>
 <path>
  <list>
    <value>1</value>
    <value>12</value>
    <value>13</value>
    <value>14</value>
    <value>15</value>
   </list>
  </path>
</root>

Use the GetEnumerable() method:

List<int> list = root
    .GetEnumerable("path/list/value", xvalue => xvalue.Get(null, int.MinValue));
    .ToList();

To set a new list of ints:

var list2 = new int[] { 1, 3, 4, 5, 6, 7, 8, 9, 0 };
root.SetEnumerable("path/list", list2, a => new XElement("value", a));

Which results in this new xml:

<root>
  <path>
    <list>
      <value>1</value>
      <value>3</value>
      <value>4</value>
      <value>5</value>
      <value>6</value>
      <value>7</value>
      <value>8</value>
      <value>9</value>
      <value>0</value>
    </list>
  </path>
</root>
0
RoboJ1M On

We use Linq To Xsd to create strongly types class wrappers around an XDocument.

You just write a schema file (Xsd) and include it in your project (once you've hacked LinqToXsd into the csproj file:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Foo1">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Range1">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="X" type="xs:int"/>
                            <xs:element name="Y" type="xs:int"/>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Then you can access classes of the same name:

Foo1 foo = new Foo1()
{
    Range1 = new Foo1.Range1()
    {
        X = 7,
        Y = 10,
    }
};

foo.Save(foo.xml);

var input = Foo1.Load(input.xml);

Console.WriteLine(input.Range1.X.ToString());

J.

1
Aliostad On

Your best bet is to use XmlSerialization. So serialize to XML and then deserialize to objects then you do not have to use casting.

Sometimes XML is created by another means other than serialization. Yet you can still create classes that represent your XML structure and deserialise.

For example:

Public Class Foo
{
   public Range Range {get; set;}
}


public class Range
{
    public int X {get; set;}
    public int Y {get; set;}
}

Then you use this:

XmlSerializer ser = new XmlSerializer(typeof(Foo));
2
psubsee2003 On

I frequently use an XSD to validate the XML structure, partially for this exact reason and since I don't have default constructors and/or have private fields, XMLSerialization is not often an option either.

If the XML validates without errors, then I go ahead and use Linq2Xml and use the various Parse(String s) methods to get data in the type my class requires, but I have not found a clean solution yet that will do this without any kind of conversion.

The validation steps avoids exceptions due to an incorrect data type.

var query = from tst in xml.Elements("test")
            select new 
            {
                int1 = Int32.Parse(tst.Element("int1").Value), 
                double1 = Double.Parse(tst.Element("double1").Value), 
                double2 = Double.Parse(tst.Element("double2").Value), 
            }

EDIT: Added info to respond to comment"

You can create an XSD directly from the Visual Studio GUI and there are other tools that do it too, but I generally just use Visual Studio. Open the XML in the editor, then from the XML menu, select "Create Schema" (path is shown in the snapshot).

XML to XSD in Visual Studio IDE

The resulting XSD is very basic. It tried to go through and "guess" the appropriate data type for each node, and it does not include any additional restrictions, but it does a decent job of putting the framework together for you.

Once that is done, you can go in and tweak the data types to better fit your needs (if desired) and you can add your own restrictions to the data (such as requiring that an xs:int value be in a range of 0 and 50 or an xs:string value be less than 10 characters long - there are dozens of other possibilities, but that should give you the idea).

I actually just played around with the XSD language and it go progressively easier the more I did it. The W3Schools site was invaluable.