Serializing and Searching Object Collections - vb.net

I would appreciate if somebody could answer some questions regarding storing and searching object collections please. If you could give a basic example of any suggestions I would be very grateful.
Storing:
I'm writing an application which needs to search Active Directory for computer objects, and I'm storing them in a collection of objects. I want to save the computer objects collection along with additional information which is not stored in Active Directory (e.g. the computer's MAC address) for the next time the application is run. I also want to save a list of OU's.
Here is my object collection so far (It will have more properties). What is the best method of saving the collection? Preferably not using a database or will saving to a file have a drastic performance impact?
Or is there a better way to do what I have below?
Public Class Computer
Public Property Name As String
Public Property FQDN As String
Public Property Path As String
End Class
Public Class ComputerCollection
Inherits Collections.CollectionBase
Public Sub Add(ByVal computer As Computer) 'Adds a new computer object to the collection
List.Add(computer)
End Sub
End Class
Searching:
I have a layout similar to ADUC with a tree view of OU's and a list view displaying computer objects in the selected OU. The following code loops through the computer object collection checking if the path of the computer object matches the selected OU path and then displays them in the list view.
Is this the best method in terms of performance? or is there a faster way?
Private Sub tvOU_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles tvOU.AfterSelect
lvComputers.Items.Clear()
If tvOU.SelectedNode.Name > "" Then
For Each computerObj In computers
If computerObj.Path.ToString.Replace("CN=" + computerObj.Name + ",", "") = tvOU.SelectedNode.Name Then
Dim lvItem As New ListViewItem
lvItem.Text = computerObj.Name
lvComputers.Items.Add(lvItem)
End If
Next
Else
Exit Sub
End If
End Sub

A simple List(Of Computer) is indeed all you need unless there is some other unknown requirement. Saving is very simple using serialization for this type of thing, and would work the same for a List or Collection<T>.
Imports System.Collections.ObjectModel
Public Class ComputersCollection
Inherits Collection(Of Computer)
' NOT the crude thing in the VB NameSpace
' there is almost nothing to add: item, add and remove
' are in the base class
' ... perhaps saving and retrieving them by a key (name)
' which may do away with the search procedure in the posted code
End Class
Private Computers As New Collection(Of Computer)
' or
Private Computers As New List(Of Computer)
The other part, saving your collection, can be simple with serialization. First, you will have to add the Serializable attribute to your Computer class:
<Serializable>
Public Class Computer
If you forget, you get the error Class foobar is not marked as serializable. Save to disk:
Imports System.Runtime.Serialization
Private myComputerFile As String = ...
Dim bf As New BinaryFormatter
Using fs As New FileStream(myComputerFile , FileMode.OpenOrCreate)
bf.Serialize(fs, Computers)
End Using
Then you can recreate the List or Collection just as easily; this time the List:
' ToDo: add a check to skip if file is not there
' as in the first time run
Dim bf As New BinaryFormatter
Using fs As New FileStream(myComputerFile , FileMode.Open)
Computers = CType(bf.Deserialize(fs), List(Of Computers))
End Using
Alternatively you can use the XML serializer to create an XML File or use the faster, smarter binary serializer is ProtoBuf-Net

Related

VB store list of records in a text file and read back

I have a VB program in VS2017. It reads many excel files and store relevant data in
Dim total_sessions As New List(Of Session_CSV_file)
The list is big (~10k), and includes about 20 fields each.
Is there a way in VB to store it in a file in one command and read it later on easily., without reading and writing each field?
Often I need to debug the later part. I prefer doing it without rerunning the whole first part.
Or is there a way in Visual Studio, to stop the run at a certain point, and store this point in a dump file, like simulators often do? (so they can run the simulation from this point later on)
Perhaps if you load your csv data into a DataTable instead:
Dim dt as New DataTable
dt.Columns.Add("Name")
dt.Columns.Add("Age", GetType(Int32))
For Each filepath in tenKFilePaths
For Each line in File.ReadAllLines(file)
Dim bits = line.Split(","c)
dt.Rows.Add(bits(0), Convert.ToInt32(bit(1))
Next line
Next filepath
Then you can save the whole lot to xml:
dt.WriteXml("c:\temp\my.xml")
Have a coffee, reboot etc the read them again and carry on where you left off:
Dim dt2 as New DataTable
dt2.ReadXml("c:\temp\my.xml")
Raw datatables are a bit difficult to work with because you end up accessing their rows and column data by string indexing and having to cast it a lot - fairly lame. There is a better way to use them, as visual studio can create custom classes that inherit from the base datatables and offer lots of functionality that does away with all the awkwardness of dealing with datatables as a very generic thing. Just like the forms designer creates a UI form that inherits from Form, the dataset designer creates custom datatables that inherit from the base
If you add a new DataSet type object to your project, double click it it opens something like a database designer. Right click that surface and choose Add DataTable, Add your Columns with their respective datatypes etc - this is the equivalent of your Session_Csv_File class, with its properties and fields. Double click on it and you can add code etc - if your session_csv_file has custom methods you can transfer their code to the custom data row
Imagine I created a new datatable in a DataSet and called it SessionCsvFiles. Imagine I added a Name and Age column, and I added a custom method called BlahBlah
I'd then be able to say in code things like:
Dim dt as New MyCustomDataSet.SessionCsvFilesDataTable
'it's like making a new SessionCsvFile
Dim r = dt.NewSessionCsvFilesRow()
'It's like setting your properties
r.Name = csvBits(0)
r.Age = Convert.ToInt32(csvBits(1))
'It's like adding your file to the List
dt.Add(r)
You now have virtually the same thing as you had before - a collection of objects that represents things about your csv files. The difference is with this route it has an already-built-in way of saving all the properties and the collection to xml and reading it back again
Add the Serializable attribute to the Class. I added a parameterized constructor for ease in building my list but you must provide a constructor without parameters for this to work. Thus, the empty Sub New.
<Serializable()>
Public Class Session_CSV_file
Public Property ID As Integer
Public Property Name As String
Public Property Type As String
Public Sub New()
End Sub
Public Sub New(SessID As Integer, SessName As String, SessType As String)
ID = SessID
Name = SessName
Type = SessType
End Sub
End Class
The add Imports System.Xml.Serialization to the top of the file.
Private Sub SaveList()
Dim serializer = New XmlSerializer(GetType(List(Of Session_CSV_file)))
Using writer As New StreamWriter("C:\Users\xxx\Documents\XMLtest.xml")
serializer.Serialize(writer, lst)
End Using
End Sub
I filled my list as follows but you can fill you list any way you choose.
Private lst As New List(Of Session_CSV_file)
Private Sub FillList()
Using cn As New SqlConnection(My.Settings.CoffeeConnection),
cmd As New SqlCommand("Select Top 10 * From Coffees;", cn)
cn.Open()
Dim reader = cmd.ExecuteReader
While reader.Read
Dim sess As New Session_CSV_file(reader.GetInt32(0), reader.GetString(1), reader.GetString(3))
lst.Add(sess)
End While
End Using
End Sub
To recreate the list... (OOps! forgot this)
Private RehydratedList As New List(Of Session_CSV_file)
Private Sub CreateListFromXML()
Dim serial As New XmlSerializer(GetType(List(Of Session_CSV_file)))
Using fs As New FileStream("C:\Users\maryo\Documents\XMLtest.xml", FileMode.Open)
RehydratedList = DirectCast(serial.Deserialize(fs), List(Of Session_CSV_file))
End Using
For Each item In RehydratedList
Debug.Print(item.ToString)
Next
End Sub

"Best" way to access IO.FileInfo Protected property (OriginalPath)?

I'm working on a series of methods that will test a given path to determine if it is (or, at least could be) valid. I have a couple of overloads that will accept either an IO.FileInfo object or an IO.DirectoryInfo object - which I'd personally prefer to use to help in catching invalid characters as soon as possible in the process. One piece of information I "need" to get as a part of the process is basically the original value the user presented - especially in the case of an IO.FileInfo or IO.DirectoryInfo object as those will automatically prepend the Environment.CurrentDirectory value to any string it determines to be a relative path.
For example:
Dim TestFile As New IO.FileInfo("Testing\This is a test")
Instantiates an IO.FileInfo object with the following properties (among others):
DirectoryName - "C:\Development\My Solution\My Project\bin\Debug\Testing"
Exists - False
Extension - ""
FullName - "C:\Development\My Solution\My Project\bin\Debug\Testing\This is a test"
Name - "This is a test"
OriginalPath - "Testing\This is a test"
This last property is the one I'm most interested in. It's a Protected property of the IO.FileInfo class, which is inherited from the IO.FileSystemInfo class, but it seems to be the best possible way to retrieve the original value passed to the IO.FileInfo constructor. Obviously, I can't access the property directly with a simple Dim TestPath As String = TestFile.OriginalPath - the IDE gives shows the error "'System.IO.FileSystemInfo.OriginalPath' is not accessible in this context because it is 'Protected'" - so I can only think of one way to get this specific piece of information: Create a whole new class that inherits IO.FileSystemInfo with a Public property for the OriginalPath that reads from the base class.
PLEASE NOTE: I do have an overloading method that accepts a String value, but I was actually thinking of trying to "hide" that one behind a Private modifier so that only FileInfo and DirectoryInfo objects could be used to call this method. My primary reasoning there is that, if a FileInfo or DirectoryInfo object fails to instantiate due to invalid characters in the string (or any other reason), there's no reason for me to make the call to this method and I may be able to "clean up" the code a little.
Regardless, the creation of a whole separate class that's really only going to be used to access this one property in this one specific scenario seems like an awful lot of overhead for what should (or, at least could) be a fairly simple thing. Unfortunately, I can't think of any other way to extract that information and I'm just wondering if I may be over-thinking this.
In the meantime, I've gone ahead and created a TempFileInfo class for testing (NOTE: I haven't actually tested it yet) without explicitly implementing any "custom" code for any of the regular Public properties (required when inheriting from the IO.FileSystemInfo class):
Private Class TempFileInfo
Inherits IO.FileSystemInfo
Public Overrides ReadOnly Property Name As String
Get
Throw New NotImplementedException()
End Get
End Property
Public Overrides ReadOnly Property Exists As Boolean
Get
Throw New NotImplementedException()
End Get
End Property
Public Overrides Sub Delete()
Throw New NotImplementedException()
End Sub
Public Shadows ReadOnly Property OriginalPath As String
Get
Return MyBase.OriginalPath
End Get
End Property
Public Shared Widening Operator CType(v As FileInfo) As TempFileInfo
Dim FSI As FileSystemInfo = v
Return CType(FSI, TempFileInfo)
End Operator
End Class
You'll note that I also included a Widening Operator that should allow me to convert from a "regular" IO.FileInfo object to my TempFileInfo object so that I can access my "custom" OriginalPath property. This is the only way I've found so far to "get there from here", but I wanted to go ahead and ask here in case there are other suggestions or things I might be overlooking.
The only "saving grace" for this method of implementation is that, because the "custom" TempFileInfo, the standard IO.FileInfo, and the standard IO.DirectoryInfo classes all inherit from the IO.FileSystemInfo class, I should be able to simply create an overloaded Widening Operator method for converting both IO.FileInfo objects as well as IO.DirectoryInfo objects instead of having to create a separate TempDirInfo class.
The path entered when a FileInfo or DirectoryInfo object is created is stored in the protected String OriginalPath field - which is used for serialization purposes - and the internal DisplayPath property, which is used as an internal reference.
When the class object is initialized (see the Init() method in the .Net source code), the path entered is stored in both the OriginalPath Field and the DisplayPath Property.
Both classes don't provide a public property to retrieve the initial path used to create the object.
They both, instead, return the DisplayPath property value as an override to the ToString() method:
Dim fInfo = New FileInfo("\SomePath\SomeFileName.ext")
Dim originalPath = fInfo.ToString()
var fInfo = new FileInfo(#"\SomePath\SomeFileName.ext");
string originalPath = fInfo.ToString();

Dictionary comes back from cache as Null/Nothing

I'm writing some vb.net code to retrieve some data from middleware, do some stuff with it, and then save a dictionary of name/value pairs to cache for quick retrieval when needed.
It all seems to go well until I retrieve the object from cache and it is always Null/Nothing.
Yet in the watch window I can see my cached object and it has data in it.
Thinking maybe it's a serialization issue I made a new class that inherits Dictionary and is marked as serializable but I still get the same results.
So right now I have this class:
<Serializable()> Public Class SerialDict
Inherits Dictionary(Of String, String)
Public Sub New()
End Sub
End Class
I populate it and put it into cache like this:
Dim Licenses As New SerialDict
For Each r As DataRow In dtLicenses.Rows
Dim prikey As String = r("SettingID").ToString.Trim
Dim decryptionKey As String = GetHash((xx))
Dim licData As String = DecryptData(r("SettingVal"), decryptionKey)
Licenses.Add(r("SettingKey"), licData)
Next
If IsNothing(HttpContext.Current.Cache("Licenses")) Then
HttpContext.Current.Cache.Add("Licences", Licenses, Nothing, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, Nothing)
End If
Then elsewhere we need to check that data so I try to retrieve it like this:
Dim Licences As SerialDict = CType(HttpContext.Current.Cache("Licenses"), SerialDict)
At this point Licenses is always Nothing, but the watch window shows data in HttpContext.Current.Cache("Licenses").
Any suggestions? Thanks!

Saving user's DataGridViewColumn widths for multiple forms/controls

Typically, when designing a DataGridView, I try to size the columns so that nothing will need to be resized by the user. This practice works most of the time, but I recently had a user change her Windows settings so that text would display larger than usual.
That single act broke all of the tedious sizing that I worked so hard on. I have looked into saving column width per user and allowing them to be saved to the registry. The issue I run into is having to create a field in the application settings for each and every value that I want to save to the registry.
When saving settings for a single form, that is not a problem, and I do use the application settings for this purpose to save the main window size/location so that users can determine the optimal view of the application.
My question is:
Is there a way to save an array to the registry, or perhaps otherwise save dynamic values into the registry, without adding these values to the application settings in advance? Ideally, I will just have a "ColumnWidths" application setting or something along those lines, and dynamically add column name/width for any column that is resized by the user.
I have the following code, which is fine if I add a new setting for each individual form that I want to save sizing for, but I'm hoping to achieve this with a single setting, that will save column sizing for multiple DataGridView/Forms.
Private Sub SaveColumnSettings()
If My.Settings.CustomColumnWidths Is Nothing Then
My.Settings.CustomColumnWidths = New StringCollection
End If
For Each dc As DataGridViewColumn In grdBackOrderedItems.Columns
My.Settings.CustomColumnWidths.Add(dc.Width.ToString())
Next
End Sub
Private Sub LoadColumnSettings()
For i As Integer = 0 To My.Settings.CustomColumnWidths.Count - 1
grdBackOrderedItems.Columns(i).Width = CInt(My.Settings.CustomColumnWidths(i))
Next
End Sub
Settings allows for a Collection type, however it is a string collection (initial post seemed not to know this).
The current edit is dicey since it assumes one collection will fit all DGVs on all forms (that is, the same number of columns for all of them) and you are only adding to the Collection every time you save - making it larger. Since you want to save the users optimal view you should have one collection per DGV since they might well make col4 on DGVFoo wider than elsewhere.
Rather than adding a collection Setting for each DGV (ie "DGVFooColumns", "DGVBarColumns" etc), which requires each one be hard coded to map to the correct entry, I would use a custom class and serialize it.
Most everything regarding them can be internalized to the class. This is not a finished class, but a rough out of making one class service any and all DGVs (it should be very close):
' for the collection
Imports System.Collections.ObjectModel
Imports System.Runtime.Serialization.Formatters.Binary
<Serializable>
Public Class GridLayouts
<Serializable>
Friend Class GridLayout
Public Property GridName As String
Public Widths As New List(Of Integer)
' Some Serializers will require a simple ctor
Public Sub New()
Widths = New List(Of Integer)
End Sub
Public Sub SaveLayout(name As String, dgv As DataGridView)
' something the code uses to map them
' other than "DataGridView1" of which there may be several
GridName = name
' ToDo: loop thru DGV save widths to Widths
End Sub
End Class
Private mCol As Collection(Of GridLayout)
Private myFile As String
Public Sub New(filename As String)
myFile = filename
mCol = New Collection(Of GridLayout)
End Sub
Private Function IndexOf(grdName As String) As Integer
For n As Integer = 0 To mCol.Count - 1
If grdName = mCol(n).GridName Then
Return n
End If
Next
Return -1 ' not found
' alternatively find by name:
'Dim item = mCol.FirstOrDefault(Function(i) i.GridName = grdName)
'Return item
End Function
Public Sub StoreLayout(name As String, dgv As DataGridView)
Dim ndx As Integer = IndexOf(name)
If ndx <> -1 Then
mCol.RemoveAt(ndx) ' throw away old one
End If
Dim grd As New GridLayout
grd.SaveLayout(name, dgv)
mCol.Add(grd)
End Sub
Public Sub RestoreLayout(name As String, dgv As DataGridView)
Dim ndx As Integer = IndexOf(name)
If ndx = -1 Then
Exit Sub
End If
For n As Integer = 0 To mCol(ndx).Widths.Count - 1
' loop thru grid and set the columns
' maybe check that the col sizes are equal
dgv.Columns(n).Width = mCol(n).Widths(n)
Next n
End Sub
Public Sub Save()
' ToDo: Make a backup (?)
' Add a Try/Catch and convert to function
Dim bf As New BinaryFormatter
Using fs As New FileStream(myFile, FileMode.OpenOrCreate)
bf.Serialize(fs, mCol)
End Using
End Sub
Public Sub Load()
Dim bf As New BinaryFormatter
Using fs As New FileStream(myFile, FileMode.Open)
mCol = CType(bf.Deserialize(fs), Collection(Of GridLayout))
End Using
End Sub
End Class
Notes:
1) The Example has SaveLayout at the Class-Item level and RestoreLayout at the Collection Level. I would pick one way or the other not split it. This is was for illustrative purposes. I would generally favor the ClassItem Level for both.
2) Rather than the BinaryFormatter, you can use the XML Serializer. I personally loathe it.
3) Note that the collection class saves/loads its own data.
4) System.Collections.ObjectModel is required for Collection(of T) and prevent VB from wanting the use the vile VisualBasic Collection (which stores only Object).
5) Code elsewhere could be optimized so that you only store a layout if they actually change the widths and click something (or have an AutoSaveChanges option).
6) A fair amount is riding on the names being unique so that the layouts can be found.
Usage
Dim SaveFile As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
CompName,
ProgramName,
filename)
' e.g C:\Users\Ziggy\MyCompany\ThisProd\GridSettngs.bin
' application-wide collection of grid layouts for this user
Private myGrids As New GridLayouts(SaveFile)
...
myGrids.StoreLayout("Customers", datagridView12)
myGrids.RestoreLayout("Orders", datagridView34)

Reading and writing to an xml file with VB.Net

I am rather new to the programming world ( I'm a network guy ). I have however been asked to develop a front end that configures an xml file for a console application. The console application reads from this xml file and opens multiple instances of a browser, one instance per monitor (6 monitors in total). There are 4 control centers, each of which has 6 monitors. Each control center is run off a seperate pc. These pc's are not on the network and do not have access to each other. I have been told I can also not use a database.
Each monitor will display one website at a time, there could be multiple sites listed to be displayed on the specific monitor so they will periodically change. Each control center will show different sites.
My first question: Is this XML valid?
<ControlCenter>
<Monitor>
<monitor_id>0</monitor_id>
<browser_short_na>ie</browser_short_na>
<url_list>
<url>
<url_id>0</url_id>
<url_na><![CDATA[http://www.hmv.com]]></url_na>
<parameter><![CDATA[]]></parameter>
</url>
<url>
<url_id>1</url_id>
<url_na><![CDATA[http://www.amazon.com]]></url_na>
<parameter><![CDATA[]]></parameter>
</url>
<url>
<url_id>2</url_id>
<url_na><![CDATA[http://www.google.com]]></url_na>
<parameter><![CDATA[]]></parameter>
</url>
</url_list>
</Monitor>
<Monitor>
<monitor_id>1</monitor_id>
<browser_short_na>ie</browser_short_na>
<url_list>
<url>
<url_id>0</url_id>
<url_na><![CDATA[http://www.amazon.com]]></url_na>
<parameter><![CDATA[]]></parameter>
</url>
</url_list>
</Monitor>
</ControlCenter>
What I do so far is open the xml file and add all the monitors to a combobox
Dim dom As New Xml.XmlDocument
dom.Load("test.xml")
ComboBox1.Items.Clear()
Dim monitorid As String = String.Empty
For Each node As Xml.XmlNode In dom.SelectNodes("//ControlCenter/Monitor/monitor_id")
monitorid = node.InnerText
ComboBox1.Items.Add(monitorid)
Next
This is now where I am stuck. Once the users selects one of the monitors from the combobox I then need to get all the information for that monitor. So I need the browser_short_na, and all the urls all based on the monitor_id selected.
I have tried creating a dataset, loading the xmlfile using readxml. I then tried creating a dataview pointing to that dataset. Tried adding a RowFilter to the dataview.
Dim val As String = ComboBox1.SelectedItem.ToString
Dim dsXmlFile As New DataSet
dsXmlFile.ReadXml("test.xml")
Dim dv As New DataView
dv.Table = dsXmlFile.Tables(0)
Dim drv As DataRowView
dv.RowFilter = "monitor_id = " & val
Dim url As String = ""
'Retrieve my values returned in the result
For Each drv In dv
url = drv("url_na")
Next
When I step through the code and look at the for each loop it fails with the message "url_na is neither a DataColumn nor a DataRelation for table Monitor."
I am thinking I am not handling the url_list section correctly.
Once all the information for the selected monitor is read I will display the values in textboxes/listboxes which the users can then edit. If they then save it should write the new values to the xml file. They could also choose to add additional urls to the list, or even create an entirely new monitor section.
Any help/suggestions would be greatly appreciated.
I would go the other way around for your issue:
Define a Class (or several classes) that will hold the data you need.
Use a simple serializer to load/save from/to that file.
If you do so, then your issue with the selection is just a classical WPF issue.
Public Class Monitor
Public Property MonitorId As integer
Public Property ListOfUrl As List(Of String)
End Class
MonitorsConfiguration will then reference a List(Of Monitor) object.
You can use a ViewModel object to handle a MonitorsConfiguration easily.
This ViewModel has a SelectedMonitorIndex property, and updates a UrlForThisMonitor List of url when the index property changes.
(Obviously it should implement INotifyPropertyChanged)
Ok so a little view on what the ViewModel might look like :
Public Class MonitorsConfigurationVM
Implement INotifyPropertyChanged
' creates a new VM. Throws exception if file name is not valid
Public Sub New(ConfigFileName As String)
_FileName = ConfigFileName
_MonitorsConfiguration = // Deserialization of the file //
_MonitorIndex = 0
End Sub
Public Property MonitorIndex As integer
Get
return _MonitorIndex
End Get
Set (Value)
if (_MonitorIndex = value) then return
' you might want to perform check here and allow only valid index
_MonitorIndex = value
_UrlIndex=0
NotifyPropertyChanged("MonitorIndex")
NotifyPropertyChanged("MonitorUrls")
NotifyPropertyChanged("HasUrl")
NotifyPropertyChanged("UrlIndex")
End Set
End Property
Public ReadOnly Property HasUrl As Boolean
Get
return Not (MonitorUrls Is Nothing OrElse MonitorUrls.count = 0 )
' ( might be used to disable the listbox bound to MonitorUrl )
End Get
End Property
Public ReadOnly Property MonitorUrls As List(Of String)
Get
return _MonitorConfiguration(_MonitorIndex).ListOfUrl '(you might want to chk)
End Get
End Property
Public Property UrlIndex As Integer
Get
return _UrlIndex
End Get
Set (value)
if value = _UrlIndex then return
' you might want to perform some check here
_UrlIndex = value
NotifyPropertyChanged("UrlIndex")
End Set
End Property
' And Also : AddMonitor / AddUrl / SaveConfiguration / ...
Private _FileName As String = Nothing
Private _MonitorsConfiguration As List(Of Monitor)=Nothing
Private _MonitorIndex As integer = 0
Protected Sub NotifyPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
You should use XPath for parsing the XML Document.
Load the xml file in XMLDocument
Create a XMLNodeList and use XPath to select nodes from loaded xml
document.
Then parse the nodes list and extract all info for selected monitor
node based on id.
Here are XPath expression helpful to you:
For selecting monitor nodes from file: "/ControlCenter/Monitor"
For selecting browser_na field based on monitor id: "/ControlCenter/Monitor[monitor_id='0']/browser_short_na"
For selection urls based on monitor id: "/ControlCenter/Monitor[monitor_id='0']/url_list/url/url_na"
Hope it works for you.