[RESOLVED] Navigating through XML
I've got the following XML document that contains application settings:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<Environment>
<Live>
<Database>LiveDB</Database>
<Server>LiveServer</Server>
<UserID>LiveID</UserID>
<Password>LivePassword</Password>
<DatabaseType>System.Data.SqlClient</DatabaseType>
</Live>
<UAT>
<Database>UATDB</Database>
<Server>UATServer</Server>
<UserID>UATID</UserID>
<Password>UATPassword</Password>
<DatabaseType>System.Data.SqlClient</DatabaseType>
</UAT>
<Development>
<Database>DevDB</Database>
<Server>DevServer</Server>
<UserID>DevID</UserID>
<Password>DevPassword</Password>
<DatabaseType>System.Data.SqlClient</DatabaseType>
</Development>
</Environment>
I can get things at the end of a chain reasonably simply. For example, this code:
Code:
Dim xpathDoc As XPathDocument = New XPathDocument("C\SettingsDocument.xml")
Dim xmlNav As XPathNavigator = xpathDoc.CreateNavigator()
Dim xmlNI As XPathNodeIterator
xmlNI = xmlNav.Select("/Environment/UAT/Database")
While xmlNI.MoveNext()
MessageBox.Show(xmlNI.Current.Value)
End While
... will return "UATDB". Easy.
The problem is that I'd like to get hold of a list of the environments, i.e. Live, UAT and Development. I had imagined that using the above code but swapping out the line
Code:
xmlNI = xmlNav.Select("/Environment/UAT/Database")
for
Code:
xmlNI = xmlNav.Select("/Environment")
would do it, but it doesn't - it basically retuns a string of all the end values: "LiveDBLiveServerLiveIDLivePasswordSystem.Data.SqlClientUATDBUATServerUATIDUATPasswordSystem.Data.Sq lClientDevDBDevServerDevIDDevPasswordSystem.Data.SqlClient". Not helpful.
So, does anyone know how I can get a list of the those environments out of that XML?
Much obliged. :)
Re: Navigating through XML
Here's a couple of ways of going about it. I normally go about it using the methods shown in dev2 or dev3 depending on whether I need to have a strongly typed variable or whether an anonymous type will work.
vb.net Code:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Load xml file into XDocument
Dim xdoc = XDocument.Load("C:\Temp\Environment.xml")
'Select XElements from xdoc
Dim selectedNodes = (From n In xdoc.<Environment>
Select n.<Live>, n.<UAT>, n.<Development>).FirstOrDefault()
'Strongly typed from selectedNodes.Development
Dim dev = (From n In selectedNodes.Development
Select New Development With
{
.Database = n.<Database>.Value,
.Server = n.<Server>.Value,
.UserId = n.<UserID>.Value,
.DevPassword = n.<Password>.Value,
.DatabaseType = n.<DatabaseType>.Value
}).FirstOrDefault()
'Strongly typed from xdoc's descendant <Development>
Dim dev2 = (From n In xdoc...<Development>
Select New Development With
{
.Database = n.<Database>.Value,
.Server = n.<Server>.Value,
.UserId = n.<UserID>.Value,
.DevPassword = n.<Password>.Value,
.DatabaseType = n.<DatabaseType>.Value
}).FirstOrDefault()
'Anonymous type rather than using Development class
Dim dev3 = (From n In xdoc...<Development>
Select New With
{
.Database = n.<Database>.Value,
.Server = n.<Server>.Value,
.UserId = n.<UserID>.Value,
.DevPassword = n.<Password>.Value,
.DatabaseType = n.<DatabaseType>.Value
}).FirstOrDefault()
End Sub
End Class
Public Class Development
Public Property Database As String
Public Property Server As String
Public Property UserId As String
Public Property DevPassword As String
Public Property DatabaseType As String
End Class
Re: Navigating through XML
I'd probably lay out the XML a little different from the start...
Code:
<Environments>
<Environment Name="Live">
<Database>LiveDB</Database>
<Server>LiveServer</Server>
<UserID>LiveID</UserID>
<Password>LivePassword</Password>
<DatabaseType>System.Data.SqlClient</DatabaseType>
</Environment>
...
</Environments>
Or even
Code:
<Environments>
<Environment Name="Live" Datablase="LiveDB" Server="LiveServer" UserID="LiveID" Password="LivePassword" Type="System.Data.SqlClient" />
...
</Environments>
-tg
Re: Navigating through XML
I would use XML serialization.
Code:
Option Strict On
Option Explicit On
Imports System.Xml.Serialization
Namespace DataBase
<XmlRoot("Environment")> _
Public Class Environments
Public Development As Information
Public Live As Information
Public UAT As Information
End Class
Public Class Information
Public Database As String
Public Server As String
Public UserID As String
Public Password As String
Public DatabaseType As String
End Class
End Namespace
Code:
Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'get data from file
Dim xmlData = IO.File.ReadAllText("C:\temp1\Environment.xml")
'create object from data
Dim o = DeserializeFromString(xmlData)
End Sub
Private Function DeserializeFromString(ByVal input As String) As DataBase.Environments
'Convert to byte array
Dim encoding As New System.Text.UTF8Encoding
Dim bytes() As Byte = encoding.GetBytes(input)
'read bytes into stream
Dim ms As New System.IO.MemoryStream
ms.Write(bytes, 0, bytes.Length)
ms.Position = 0
'get object from bytes
Dim xs As New Xml.Serialization.XmlSerializer(GetType(DataBase.Environments))
Return DirectCast(xs.Deserialize(ms), DataBase.Environments)
End Function
Re: Navigating through XML
Thanks for all the advice, folks. Unfortunately, MattP's code is for 2010 and Bill's seems to need me to already know what the possible environments are, when the problem is that I won't know until I extract them from the XML. It's quite possible I've misunderstood, though. :)
I've tweaked the XML slightly (not quite as suggested by TG), and I've now been able to do what I was after:
Code:
<Environments>
<Environment>
<EnvironmentName>Live</EnvironmentName>
<Database>LiveDB</Database>
<Server>LiveServer</Server>
<UserID>LiveID</UserID>
<Password>LivePassword</Password>
<DatabaseType>System.Data.SqlClient</DatabaseType>
</Environment>
...
</Environments>
Code:
Public Shared Function GetEnvironments() As List(Of String)
Dim Environments As List(Of String) = New List(Of String)
Try
'# Our configuration file holds properties based on the environment.
'# Return a list of these environments.
Dim xmlDoc = XDocument.Load(SettingsDocument)
For Each EnvironmentXML In xmlDoc...<Environment>
Environments.Add(EnvironmentXML...<EnvironmentName>.Value)
Next
Return Environments
Catch ex As Exception
WriteLog(ex)
Return Environments
End Try
End Function
The only problem is, I now can't do something which I could with the original layout - that is, to find a specific element while using a variable:
Code:
Public Shared Function GetApplicationSetting(ByVal settingName As String) As String
Dim Setting As String = String.Empty
Try
'# Setting is held in the configuration file as a global setting.
Dim xpathDoc As XPathDocument = New XPathDocument(SettingsDocument)
Dim xmlNav As XPathNavigator = xpathDoc.CreateNavigator()
Dim xmlNI As XPathNodeIterator
xmlNI = xmlNav.Select("/Environment/Global/" & settingName)
While xmlNI.MoveNext()
Setting = xmlNI.Current.Value
End While
Return Setting
Catch ex As Exception
WriteLog(ex)
Return Setting
End Try
End Function
Stepping though this shows that it steps straight out of the loop, meaning that there's no .MoveNext to be performed. The xmlNI.Current.Value is back to being "LiveDBLiveServerLiveIDLivePasswordSystem.Data.SqlClientUATDBUATServerUATIDUATPasswordSystem.Data.Sq lClientDevDBDevServerDevIDDevPasswordSystem.Data.SqlClient".
I think I'm just making matters worse!
Re: Navigating through XML
By the way, I can obviously see why that old code isn't working - I just can't see what I need to change it to.
Re: Navigating through XML
Okay, I've managed to get something working having rejigged my XML as suggested by TG.
Code:
Try
Dim xmlDoc As Xml.XmlDocument = New Xml.XmlDocument
xmlDoc.Load(SettingsDocument)
Dim MainNodes As Xml.XmlNodeList = xmlDoc.GetElementsByTagName("Environment")
For Each ChildNode As Xml.XmlNode In MainNodes
Debug.Print(ChildNode.Attributes.GetNamedItem("Name").Value) '# Environment name: Live, UAT etc
For Each InnerNode As XmlNode In ChildNode.ChildNodes
Debug.Print(InnerNode.LocalName & " = " & InnerNode.InnerText) '# Element, e.g. UserID = LiveUserID
Next InnerNode
Next ChildNode
Catch ex As Exception
End Try
It's a little clumsy as it needs to loop through to find everything rather than jumping to the correct node to start with, but at least it works.
Re: Navigating through XML
Quote:
Originally Posted by
InvisibleDuncan
Unfortunately, MattP's code is for 2010
It should work under 2008 with the addition of underscores at the line breaks.
Glad you got a solution that works for you though.
Re: Navigating through XML
Thanks, Matt - I hadn't realised that. I think I might investigate your suggestion a bit more, because it looks very interesting and completely different to what I've tried before. I'm all for learning something new.
In the meantime, I did find a simple way of getting at individual elements:
Code:
Dim xmlDoc As Xml.XmlDocument = New Xml.XmlDocument
xmlDoc.Load(SettingsDocument)
Dim MainNodes As Xml.XmlNodeList = xmlDoc.SelectNodes("/Environments/Environment[@Name='Development']")
For Each ChildNode As Xml.XmlNode In MainNodes
Debug.Print(ChildNode.Attributes.GetNamedItem("Name").Value) '# Environment name: Live, UAT etc
Debug.Print(ChildNode.SelectSingleNode("Database").InnerXml)
Debug.Print(ChildNode.SelectSingleNode("Server").InnerXml)
Debug.Print(ChildNode.SelectSingleNode("UserID").InnerXml)
Debug.Print(ChildNode.SelectSingleNode("Password").InnerXml)
Next ChildNode
Thanks to everyone for their help - it's much appreciated.
Re: [RESOLVED] Navigating through XML
yeah, you shouldn't have to loop through the nodes to find the one you want... you really only need to loop when you want them all, or not sure what you're looking for... the xQuery you have there should be perfect, and would be how I'd roll with it too ... until someone like Matt comes along and points out the LINQ version, and I'd go "oh sweet!" and then re-write everything... :P
-tg