简体   繁体   中英

Retrieving Data From XML File

I seem to be having a problem with retrieving XML values with C#, which I know it is due to my very limited knowledge of C# and .XML.

I was given the following XML file

<PowerBuilderRunTimes>
    <PowerBuilderRunTime>
        <Version>12</Version>
        <Files>
            <File>EasySoap110.dll</File>
            <File>exPat110.dll</File>
            <File>pbacc110.dll</File>
        </File>
     </PowerBuilderRunTime>
</PowerBuilderRunTimes>

I am to process the XML file and make sure that each of the files in the exist in the folder (that's the easy part). It's the processing of the XML file that I have having a hard time with. Here is what I have done thus far:

var runtimeXml = File.ReadAllText(string.Format("{0}\\{1}", configPath, Resource.PBRuntimes));

var doc = XDocument.Parse(runtimeXml);
var topElement = doc.Element("PowerBuilderRunTimes");
var elements = topElement.Elements("PowerBuilderRunTime");

foreach (XElement section in elements)
{
    //pbVersion is grabbed earlier. It is the version of PowerBuilder
    if( section.Element("Version").Value.Equals(string.Format("{0}", pbVersion ) ) )
    {
        var files = section.Elements("Files");

        var fileList = new List<string>();

        foreach (XElement area in files)
        {
            fileList.Add(area.Element("File").Value);
        }
    }
}

My issue is that the String List is only ever populated with one value, "EasySoap110.dll", and everything else is ignored. Can someone please help me, as I am at a loss.

Look at this bit:

var files = section.Elements("Files");

var fileList = new List<string>();

foreach (XElement area in files)
{
    fileList.Add(area.Element("File").Value);
}

You're iterating over each Files element, and then finding the first File element within it. There's only one Files element - you need to be iterating over the File elements within that.

However, there are definitely better ways of doing this. For example:

var doc = XDocument.Load(Path.Combine(configPath, Resource.PBRuntimes));
var fileList = (from runtime in doc.Root.Elements("PowerBuilderRunTime")
                where (int) runtime.Element("Version") == pbVersion
                from file in runtime.Element("Files").Elements("File")
                select file.Value)
               .ToList();

Note that if there are multiple matching PowerBuilderRunTime elements, that will create a list with all the files of all those elements. That may not be what you want. For example, you might want:

var doc = XDocument.Load(Path.Combine(configPath, Resource.PBRuntimes));
var runtime = doc.Root
                 .Elements("PowerBuilderRunTime")
                 .Where(r => (int) r.Element("Version") == pbVersion)
                 .Single();

var fileList = runtime.Element("Files")
                      .Elements("File")
                      .Select(x => x.Value)
                      .ToList();

That will validate that there's exactly one matching runtime.

The problem is, there's only one element in your XML, with multiple children. You foreach loop only executes once, for the single element, not for its children.

Do something like this:

var fileSet = files.Elements("File");
foreach (var file in fileSet) {
    fileList.Add(file.Value);
}

which loops over all children elements.

I always preferred using readers for reading homegrown XML config files. If you're only doing this once it's probably over kill, but readers are faster and cheaper.

public static class PowerBuilderConfigParser
{
    public static IList<PowerBuilderConfig> ReadConfigFile(String path)
    {
        IList<PowerBuilderConfig> configs = new List<PowerBuilderConfig>();
        using (FileStream stream = new FileStream(path, FileMode.Open))
        {
            XmlReader reader = XmlReader.Create(stream);
            reader.ReadToDescendant("PowerBuilderRunTime");
            do
            {
                PowerBuilderConfig config = new PowerBuilderConfig();
                ReadVersionNumber(config, reader);
                ReadFiles(config, reader);
                configs.Add(config);
                reader.ReadToNextSibling("PowerBuilderRunTime");
            } while (reader.ReadToNextSibling("PowerBuilderRunTime"));
        }
        return configs;
    }

    private static void ReadVersionNumber(PowerBuilderConfig config, XmlReader reader)
    {

        reader.ReadToDescendant("Version");
        string version = reader.ReadString();

        Int32 versionNumber;
        if (Int32.TryParse(version, out versionNumber))
        {
            config.Version = versionNumber;
        }
    }

    private static void ReadFiles(PowerBuilderConfig config, XmlReader reader)
    {
        reader.ReadToNextSibling("Files");
        reader.ReadToDescendant("File");
        do
        {
            string file = reader.ReadString();
            if (!string.IsNullOrEmpty(file))
            {
                config.AddConfigFile(file);
            }
        } while (reader.ReadToNextSibling("File"));
    }
}

public class PowerBuilderConfig
{
    private Int32 _version;
    private readonly IList<String> _files;

    public PowerBuilderConfig()
    {
        _files = new List<string>();
    }

    public Int32 Version
    {
        get { return _version; }
        set { _version = value; }
    }

    public ReadOnlyCollection<String> Files
    {
        get { return new ReadOnlyCollection<String>(_files); }
    }

    public void AddConfigFile(String fileName)
    {
        _files.Add(fileName);
    }
}

Another way is to use a XmlSerializer .

[Serializable]
[XmlRoot]
public class PowerBuilderRunTime
{
 [XmlElement]
 public string Version {get;set;}
 [XmlArrayItem("File")]
 public string[] Files {get;set;} 

 public static PowerBuilderRunTime[] Load(string fileName)
 {
    PowerBuilderRunTime[] runtimes;
    using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
        {
            var reader = new XmlTextReader(fs);
            runtimes = (PowerBuilderRunTime[])new XmlSerializer(typeof(PowerBuilderRunTime[])).Deserialize(reader);
        }
     return runtimes;
 }
}

You can get all the runtimes strongly typed, and use each PowerBuilderRunTime's Files property to loop through all the string file names.

var runtimes = PowerBuilderRunTime.Load(string.Format("{0}\\{1}", configPath, Resource.PBRuntimes));

You should try replacing this stuff with a simple XPath query.

        string configPath;
        System.Xml.XPath.XPathDocument xpd = new System.Xml.XPath.XPathDocument(cofigPath);
        System.Xml.XPath.XPathNavigator xpn = xpd.CreateNavigator();
        System.Xml.XPath.XPathExpression exp = xpn.Compile(@"/PowerBuilderRunTimes/PwerBuilderRunTime/Files//File");
        System.Xml.XPath.XPathNodeIterator iterator = xpn.Select(exp);
        while (iterator.MoveNext())
        {
            System.Xml.XPath.XPathNavigator nav2 = iterator.Current.Clone();
            //access value with nav2.value
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM