VB .NET HTMLAgilityPack Colon Separated Values - vb.net

Is there a way to get the values within a tag using HTMLAgilityPack?
My variable dataNode is an HtmlAgilityPack.HtmlNode and contains:
Dim doc as New HtmlAgilityPack.HtmlDocument()
doc.LoadHtml("
<div id="container" data="id:12,country:usa,city:oregon,id:13,country:usa,city:atlanta">
Google
</div>
")
Would like to get the value of each id, country,city. They repeat within the tag and have different values.
Dim dataNode as HtmlAgililtyPack.HtmlNode
dataNode = doc.documentNode.SelectSingleNode("//div")
txtbox.text = dataNode.Attributes("id[1]").value
This gives an error System.NullReferenceException

You need the "data" attribute, not the "id" attribute.
Once you have the value of the correct attribute, you will need to parse it into some data structure suitable for holding each part of the data, for example:
Option Infer On
Option Strict On
Module Module1
Public Class LocationDatum
Property ID As Integer
Property Country As String
Property City As String
Public Overrides Function ToString() As String
Return $"ID={ID}, Country={Country}, City={City}"
End Function
End Class
Sub Main()
Dim doc As New HtmlAgilityPack.HtmlDocument()
doc.LoadHtml("
<div id=""container"" data=""id:12,country:usa,city:oregon,id:13,country:usa,city:atlanta"">
Google
</div>
")
Dim dataNode = doc.DocumentNode.SelectSingleNode("//div")
Dim rawData = dataNode.Attributes("data").Value
Dim dataParts = rawData.Split(","c)
Dim locationData As New List(Of LocationDatum)
' A simple way of parsing the data
For i = 0 To dataParts.Count - 1 Step 3
If i + 2 < dataParts.Count Then
Dim id As Integer = -1
Dim country As String = ""
Dim city As String = ""
' used to check all three required parts have been found:
Dim partsFoundFlags = 0
For j = 0 To 2
Dim itemParts = dataParts(i + j).Split(":"c)
Select Case itemParts(0)
Case "id"
id = CInt(itemParts(1))
partsFoundFlags = partsFoundFlags Or 1
Case "country"
country = itemParts(1)
partsFoundFlags = partsFoundFlags Or 2
Case "city"
city = itemParts(1)
partsFoundFlags = partsFoundFlags Or 4
End Select
Next
If partsFoundFlags = 7 Then
locationData.Add(New LocationDatum With {.ID = id, .Country = country, .City = city})
End If
End If
Next
For Each d In locationData
Console.WriteLine(d)
Next
Console.ReadLine()
End Sub
End Module
Which outputs:
ID=12, Country=usa, City=oregon
ID=13, Country=usa, City=atlanta
It is resistant to some data malformations, such as id/city/country being in a different order, and spurious data at the end.
You would, of course, put the parsing code into its own function.

Related

How to change this code to find all matches

I need to perform a substring match on the ID. For example, search for all the IDs that begin with "AB" and return all matches. ID format is AB1234.
Dim CPosition, HashPostion As Integer
Dim StudentID, Fileline, IdPart As String
Dim SPostion As Char
Dim found As Boolean = False
FileOpen(1, "StudendRecord.txt", OpenMode.Input)
Console.Write("Enter ID to find the email: ")
StudentID = Console.ReadLine()
Do
CPosition = 1
Fileline = LineInput(1)
Do
SPostion = Mid(Fileline, CPosition, 1)
CPosition = CPosition + 1
Loop Until SPostion = "#"
HashPostion = Len(Fileline) - (CPosition - 1)
CPosition = 1
Do
SPostion = Mid(Fileline, CPosition, 1)
IdPart = IdPart + SPostion
If StudentID = IdPart Then
Console.WriteLine("the email: " & Right(Fileline, HashPostion))
found = True
End If
CPosition = CPosition + 1
Loop Until SPostion = "#"
Loop Until EOF(1)
If found = False Then
Console.WriteLine("ID not found.")
End If
FileClose(1)
To use this code add Imports System.IO to the top of the file. This namespace has a File class. File.ReadLines returns and array of the lines in a text file.
I created a simple class to hold the data in the file.
Then a list of the class's type.
I then split each line on the # character getting a 2 element array. The first element will contain the ID and the second element contains the email.
Create a new student and pass the data to the constructor. Then add the new student to the list.
To search the list I used Linq. Linq returns an IEnumerable(Of T) T in this case is StudentRecord.
To search the list I used Linq. I used .ToArray on the resulting IEnumerable because we only need the first element.
Public Class StudentRecord
Public Property ID As String
Public Property Email As String
Public Sub New(id As String, email As String)
Me.ID = id
Me.Email = email
End Sub
End Class
Module Module1
Private StudentRecords As New List(Of StudentRecord)
Public Sub Main()
Dim Records = File.ReadLines("StudentRecord.txt")
For Each line In Records
Dim SplitLine = line.Split("#"c)
Dim stu As New StudentRecord(SplitLine(0), SplitLine(1))
StudentRecords.Add(stu)
Next
Console.Write("Enter ID to find the email: ")
Dim StudentID = Console.ReadLine()
Dim students = From stu In StudentRecords
Where stu.ID.StartsWith(StudentID)
Select stu
If students.Count > 0 Then
For Each stu In students
Console.WriteLine($"Student {stu.ID} email is {stu.Email}")
Next
Else
Console.WriteLine("ID not found.")
End If
Console.ReadLine()
End Sub
End Module

Removing duplicates in Text Box and adding the corresponding values

I have a VB form with three TextBoxes. Here's an example of what I'd like the program to achieve:
So, that's the form ... the program sorts a text file and gets names, goals, and positions. E.g.
Jordan 26 Center
James 10 Mid
Jordan 4 Center
Jack 6 Forward
James 10 Mid
When the update button is clicked, the program should realize that James and Jordan are written twice, remove one of them and add their goals, so it should output:
Jordan 30 Center
James 20 Mid
Jack 6 Forward
To do this I've had the data transferred into ListBoxes which makes it easier to remove duplicates, the data is then transferred back into a multi-line TextBox so it is editable. Here's my code so far. It either gives the wrong results or an index out of range error.
Dim Count1 As Integer
Dim Count2 As Integer
Dim Count3 As Integer
Dim NewInt As Integer
Dim ValOne As Integer
Dim ValTwo As Integer
ListBox1.Items.Clear()
ListBox2.Items.Clear()
ListBox3.Items.Clear()
NewInt = 0
ValOne = 0
ValTwo = 0
ListBox1.Items.AddRange(Players.Text.Split(vbNewLine))
ListBox2.Items.AddRange(Goals.Text.Split(vbNewLine))
ListBox3.Items.AddRange(Positions.Text.Split(vbNewLine))
Count1 = ListBox1.Items.Count
Count2 = ListBox2.Items.Count
Count3 = ListBox3.Items.Count
If Count1 = Count2 And Count1 = Count3 And Count2 = Count3 Then
'Set two counters to compare all words with each other
For iFirstCounter As Integer = 0 To ListBox1.Items.Count - 1
For iSecondCounter As Integer = 0 To ListBox1.Items.Count - 1
'Make sure there will not be an 'out of range' error,
'because you are removing items from the listbox.
iSecondCounter = Convert.ToInt64(iSecondCounter)
iFirstCounter = Convert.ToInt64(iFirstCounter)
ListBox2.Items.RemoveAt(iSecondCounter)
ListBox2.Items.RemoveAt(iFirstCounter)
If iFirstCounter < iSecondCounter Then
ListBox2.Items.Insert(iFirstCounter, NewInt.ToString)
Else
ListBox2.Items.Insert(iSecondCounter, NewInt.ToString)
End If
Next
Next
Players.Text = ""
Goals.Text = ""
Positions.Text = ""
Dim i As Integer
For i = 0 To ListBox1.Items.Count - 1
If Players.Text = "" Then
Players.Text = ListBox1.Items(i)
Else
Players.Text = Players.Text & vbNewLine & ListBox1.Items(i)
End If
Next
Dim a As Integer
For a = 0 To ListBox2.Items.Count - 1
If Goals.Text = "" Then
Goals.Text = ListBox2.Items(a)
Else
Goals.Text = Goals.Text & vbNewLine & ListBox2.Items(a)
End If
Next
Dim b As Integer
For b = 0 To ListBox3.Items.Count - 1
If Positions.Text = "" Then
Positions.Text = ListBox3.Items(b)
Else
Positions.Text = Positions.Text & vbNewLine & ListBox3.Items(b)
End If
Next
Else
MessageBox.Show("The Text Boxes don't contain an equal number of values ... please add more/remove some values")
End If
Could be done in multiple ways, for example:
If TextBox2.Lines.Count > 1 Then
Dim LineList As List(Of String) = TextBox2.Lines.ToList 'textbox lines
Dim NewLines As List(Of String) = TextBox2.Lines.ToList 'can't edit list we're looping over, a copy of lines
Dim NamesList As New List(Of String)
For x = 0 To LineList.Count - 1
Dim linesplit As String() = LineList(x).Split({" "}, StringSplitOptions.RemoveEmptyEntries)
If NamesList.Contains(linesplit(0)) Then
NewLines.Remove(LineList(x))
Else
NamesList.Add(linesplit(0))
End If
Next
TextBox2.Lines = NewLines.ToArray
End If
Here's an example of code that does this via LINQ and Lambdas.
Module Module1
Sub Main()
Dim ungroupedPlayers(1) As String
ungroupedPlayers(0) = "Jordan 26 Center"
ungroupedPlayers(1) = "Jordan 4 Center"
Dim players = ungroupedPlayers.ToList().ConvertAll(Of Player)(Function(x As String) As Player
Dim split() As String = x.Split(" "c)
Dim p As New Player
p.PlayerName = split(0)
p.Count = split(1)
p.Position = split(2)
Return p
End Function)
Dim playersGrouped = From p In players
Group By PlayerName = p.PlayerName Into g = Group
Select PlayerName, Count = g.Sum(Function(ip As Player) ip.Count), Position = g.Min(Function(ip As Player) ip.Position.ToString())
Dim groupedPlayers() As String = playersGrouped.ToList().ConvertAll(Of String)(Function(ip)
Return ip.PlayerName.ToString() & " " & ip.Count.ToString() & " " & ip.Position.ToString()
End Function).ToArray()
For Each groupedPlayer as String in groupedPlayers
Console.WriteLine(groupedPlayer)
Next
Console.Read()
End Sub
Public Class Player
Public PlayerName As String
Public Count As Integer
Public Position As String
End Class
End Module
You don't need heavy ListBox control for working with players data.
Use List(Of T) and create class Player for better readability.
You can remove duplicates before you will display values in your form.
And instead of multiline textbox you can use DataGridView as "right tool for the editing data".
Public Class Player
Public Property Name As String
Public Property Position As String
Public Property Goals As Integer
End
Public Class PlayersForm : Form
Private Sub Form_Load(sender As Object, e As System.EventArgs) Handles MyBase.Load
Dim data As List(Of Player) = LoadPlayersData()
Dim players As List(Of Player) = NormalizeData(data)
' Use DataGridView
Me.DataGridView1.DataSource = players
End Sub
Private Function LoadPlayersData() As List(Of Player)
Dim rawData As String() = File.ReadAllLines("pathToTextFile")
Return rawData.Select(Function(line) LineToPlayer(line)).ToList()
End Function
Private Function NormalizeData(players As List(Of Player)) As List(Of Player)
Return players.Group(Function(player) player.Name)
.Select(Function(group)
Return New Player With
{
.Name = group.Key,
.Position = group.First().Position,
.Goals = group.Sum(Function(player) player.Goals)
}
End Function)
.ToList()
End Function
Private Function LineToPlayer(line As String) As Player
Dim values = line.Split(" "c)
Return New Player With
{
.Name = values(0),
.Position = values(2),
.Goals = Integer.Parse(values(1))
}
End Function
End Class
DataGridView control will automatically update your List(Of Players) when you make any change. which give you possibility to have some other controls which automatically display best scorers for example, without extra converting data from string to integer and back.

Null Exception while loading my file to listbox.

I have a problem. I'm trying to load data to listbox , but when I click start I get an unhandled exception that states values cannot be null. I've bolded where the issue is.
I'm trying to create a library database, and would really appreciate if someone could help me fix this issue. Thanks!
Public Class frmLibrary
Structure BookData
Dim Title As String
Dim Author As String
Dim ISBN As Integer
Dim YearPublished As Integer
End Structure
Dim bookinfo() As BookData 'Array
Private Sub frmLibrary_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim bookfiles() As String = IO.File.ReadAllLines("LibraryDatabase.txt")
Dim n As Integer = bookfiles.Count - 1
ReDim bookinfo(n)
Dim line As String
Dim books() As String 'books are the parts
'use the split methods to assign values to the members of the structure variable.
For i As Integer = 0 To n ---**>Why is this value null???????**
line = bookfiles(i)
books = line.Split(","c) '
bookinfo(i).Title = books(0)
bookinfo(i).Author = books(1)
bookinfo(i).ISBN = books(2)
bookinfo(i).YearPublished = books(3)
Next
Dim query = From book In bookinfo
Select book.Title, book.Author, book.ISBN, book.YearPublished
DGVBookInfo.DataSource = query.ToList
DGVBookInfo.Columns("Title").HeaderText = "Title"
DGVBookInfo.Columns("Author").HeaderText = "Author"
DGVBookInfo.Columns("ISBN").HeaderText = "ISBN"
DGVBookInfo.Columns("Year Published").HeaderText = "Year Published"
DGVBookInfo.AutoResizeColumns()
DGVBookInfo.RowHeadersVisible = False
End Sub

How can I put an extra value?

I have a table (DataGridView) like this :
Col1 | Col2 | Col3
3 | Mars | Regular
Here is my code:
For a As Integer = 0 To Form3.DataGridView1.Rows.Count - 1
For b As Integer = 0 To Form3.DataGridView1.Rows.Count - 1
For c As Integer = 0 To Form3.DataGridView1.Rows.Count - 1
If Form3.DataGridView1.Rows(c).Cells(2).Value = "Regular" Then
If Form3.DataGridView1.Rows(b).Cells(1).Value = Form3.MetroComboBox7.Items(0) Then
fair = 7 * Form3.DataGridView1.Rows(a).Cells(0).Value
Label1.Text += fair
End If
End If
Next
Next
Next
I want to set that if Regular is selected on Col3 and Mars on Col2 then the value is 7 and it will multiply by row Col1 and it will be the same every row.
I think you should use the event dtgv.CellMouseClick.
Then you create conditions that you want. I give you an example here :
Public Sub event_select() Handles dtgv.CellMouseClick
Dim row As Integer = dtgv.CurrentRow.Index()
' If the column 2 and 3 are selected
If dtgv.Rows(row).Cells(1).Selected = True And dtgv.Rows(row).Cells(2).Selected = True Then
' If the value of the 2nd column is Mars and the value of the 3rd column is Regular
If dtgv.Rows(row).Cells(1).Value = "Mars" And dtgv.Rows(row).Cells(2).Value = "Regular" Then
Label1.Text = 7 * dtgv.Rows(row).Cells(0).Value
End If
End If
End Sub
You should also check that no other rows have cells selected.
One loop for all rows is enough to calculate "fair" for all rows
Const REGULAR_VALUE As String = "Regular"
Const FAIR_COEFFICENT As Integer = 7
Dim fairSum As Integer = 0
For Each row As DataGridViewRow in DataGridView1.Rows
If REGULAR_VALUE.Equals(row.Cells(2).Value.ToString()) = False Then Continue For
If Equals(MetroComboBox7.Items(0), row.Cells(1).Value) = False Then Continue For
Dim col1Value As Integer = Integer.Parse(row.Cells(1).Value)
Dim fair As Integer = col1Value * FAIR_COEFFICENT
fairSum += fair
Next
Label1.Text = fairSum.ToString()
And set Option Strict On in your project or at least in the code file(first line of file).
This will save you time by giving fast feedback about possible type converting errors during compile time.
Create class which represent your data in strongly typed manner
Public Class Ticket
Public Property Passenger As Integer
Public Property Destination As String
Public Property Status As String
End Class
Then you can add rows to the DataGridView in easy way in your form
Public Class YourForm
Private _tickets As New BindigList(Of Ticket)()
Public Sub New()
InitializeComponent() ' Forms required method
DataGridView1.DataSource = _tickets
End Sub
Private Sub Populate(passenger As Integer, destination As String, Status As String)
Dim newTicket As New Ticket With
{
.Passenger = passenger,
.Destination = destination,
.Status = Status,
}
_ticket.Add(newTicket)
End Sub
'Then you can loop all rows with correct types
Private Sub Calculate()
Dim fairSum As Integer = 0
For Each ticket As Ticket in _tickets
If REGULAR_VALUE.Equals(ticket.Status) = False Then Continue For
If ticket.Destination.Equals(MetroComboBox7.Items(0)) = False Then Continue For
Dim fair As Integer = ticket.Passenger * FAIR_COEFFICENT
fairSum += fair
Next
Label1.Text = fairSum.ToString()
End Sub
End Class

How can I seperate the values in the textbox to show in different labels using button?

i want to be able to separate the values in a textbox and display the separated values in different labels using a button
this is what i want to happen:
input "Ac2O3" on textbox
Be able to separate "Ac", "2", "O", and "3".
Show the separated values in different textboxes using a button
im using visual basic 2012
im sorry im still new to this
thanks in advance!!
You can access different character of the string with the index.
Dim input As String = "Ac2O3"
Dim part1 As String = input(0) & input(1)
Dim part2 As String = input(2)
Dim part3 As String = input(3)
Dim part4 As String = input(4)
If you don't know how to handle the button event or how to display text in a textbox, that would be different questions.
This code creates a structure called Element to make the results from the function clearer - add this to your main class that the function is going to be placed in. The main function takes a string as it's input and produced a list of structures of Element as it's output. There probably are shorter ways to do this, but I'm a fairly basic programmer who likes a puzzle - hope this helps - Dont forget to accept the answer by clicking on the tick. If you have any queries please dont hesitate to ask
Structure Element
Dim symbol As String
Dim elementCount As Int16
End Structure
Function ParseFormula(ByVal compoundString As String) As List(Of Element)
Dim tempParseFormula = New List(Of Element)
Dim firstLetter As String = "[A-Z]"
Dim secondLetter As String = "[a-z]"
Dim number As String = "[0-9]"
Dim tempElementCount As String = ""
Dim maxIndex As String = compoundString.Length - 1
Dim i As Integer = 0
Dim parsedElement As New Element
While i <= maxIndex
Dim tempChar As String = compoundString(i)
Select Case True
Case tempChar Like firstLetter
parsedElement.symbol = parsedElement.symbol & tempChar
Case tempChar Like secondLetter
parsedElement.symbol = parsedElement.symbol & tempChar
Case tempChar Like number
tempElementCount = tempElementCount & tempChar
End Select
If i = maxIndex Then
If Val(tempElementCount) = 0 Then
tempElementCount = 1
End If
parsedElement.elementCount = Val(tempElementCount)
tempParseFormula.Add(parsedElement)
parsedElement.symbol = ""
parsedElement.elementCount = 0
tempElementCount = ""
Exit While
End If
i += 1
If compoundString(i) Like firstLetter Then
If Val(tempElementCount) = 0 Then
tempElementCount = 1
End If
parsedElement.elementCount = Val(tempElementCount)
tempParseFormula.Add(parsedElement)
parsedElement.symbol = ""
parsedElement.elementCount = 0
tempElementCount = ""
End If
End While
Return tempParseFormula
End Function