VB.NET - Adding more than 1 string to .contains - vb.net

I have an HTMLElementCollection that I'm going through using a For Each Loop to see if the InnerHTML contains certain words. If they do contain any of those keywords it gets saved into a file.
Everything works fine but I was wondering if there is a way to simplify. Here's a sample
For Each Helement As HtmlElement In elements
If Helement.InnerHtml.Contains("keyword1") Or Helement.InnerHtml.Contains("keyword2") Or Helement.InnerHtml.Contains("keyword3") Or Helement.InnerHtml.Contains("keyword4") Or Helement.InnerHtml.Contains("keyword5") = True Then
' THE CODE TO COPY TO FILE
End If
Next Helement
Does anything exist that would work like:
If Helement.InnerHtml.Contains("keyword1", "keyword2", "keyword3", "keyword4", "keyword5")
The way I'm doing it now just seems wasteful, and I'm pretty OCD about it.

1) One approach would be to match the InnerHtml string against a regular expression containing the keywords as a list of alternatives:
Imports System.Text.RegularExpressions
Dim keywords As New Regex("keyword1|keyword2|keyword3")
...
If keywords.IsMatch(HElement.InnerHtml) Then ...
This should work well if you know all your keywords beforehand.
2) An alternative approach would be to build a list of your keywords and then compare the InnerHtml string against each of the list's elements:
Dim keywords = {"keyword1", "keyword2", "keyword3"}
...
For Each keyword As String In keywords
If HElement.InnerHtml.Contains(keyword) Then ...
Next
Edit: The extension method suggested by Rob would result in more elegant code than the above approach #2, IMO.

You could write an Extension Method to string that provides a multi-input option, such as:
Public Module StringExtensionMethods
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Function Contains(ByVal str As String, ByVal ParamArray values As String()) As Boolean
For Each value In values
If str.Contains(value) Then
Return True
End If
Next
Return False
End Function
End Module
You could then call that instead, as in your second example :)

Here's another extension method that cleans up the logic a little with LINQ:
<Extension()>
Public Function MultiContains(str As String, ParamArray values() As String) As Boolean
Return values.Any(Function(val) str.Contains(val))
End Function

Related

Using a function to clean data in VBA

I am familiar with this post: How to Return a result from a VBA Function but changing my code does not seem to help.
I want to write a simple function in VBA that allows to lowercase an input sentence. I wrote this:
Private Function Converter(inputText As String) As String
Converter = LCase(inputText)
End Function
Sub test()
Dim new_output As String
new_output = Converter("Henk")
MsgBox (new_output)
End Sub
I tried following the advice I found at another stackoverflow post. I made me change this:
Private Function Converter(inputText As String)
Set outputText = LCase(inputText)
End Function
Sub test()
Dim new_output As String
Set new_output = Converter("Henk")
MsgBox (new_output)
End Sub
However, now I get an error that an object is required. Why does it require an object now? I dont get it...
Set outputText = LCase(inputText)
The Set keyword is reserved for Object data types. Unlike VB.NET, String in VBA is a basic data types.
So you dont Set a variable to a string. Drop the second version of your code altogether. It doesn't make sense. That "advice" was probably in another context.
To fix your first version
1- Assign the returned result to the name of the function Converter
2- It would be beneficial to specify explicitly the return type, as String. Currently it is a Variant that always embeds a String, so better make it explicit:
' vvvvvvvvv
Private Function Converter(inputText As String) As String
Converter = LCase(inputText) ' <------------ assign return to name of function
End Function

Checking is StringBuilder() AppendLine() is an empty string

I want to conditionally append a string, derived from a function, to a string builder. The required condition is that the function is not returning a blank string ("").
The purpose of conditionally appending the string is to avoid AppendLine() appending a line when the string (returned from the function) being appended is empty.
My current code (wrapping the function in a condition calling the very same function):
Dim builder As New System.Text.StringBuilder()
builder.Append("Some text...").AppendLine()
If Not IsNothing(someFunction(someParameterAA, someParameterAB)) Then
builder.Append(someFunction(someParameterAA, someParameterAB)).AppendLine()
End If
If Not IsNothing(someFunction(someParameterBA, someParameterBB)) Then
builder.Append(someFunction(someParameterBA, someParameterBB)).AppendLine()
End If
builder.AppendLine().Append("...some text.")
Dim s As String = builder.ToString
MessageBox.Show(s)
It is my hope that a more efficient alternative exists (efficient in terms of the amount of code to be written). Ultimately, I'd like to avoid calling the same function twice however I cannot independently add the builder.Append line of code to my function. Is it instead possible to target builder.Append?
Example of the potential logic:
If `builder.Append()` inside the following brackets is not an empty string then:
(
builder.Append(someFunction(someParameterAA, someParameterAB)).AppendLine()
)
If anyone has a solution on the above, bear in mind the prequisite of concision (=< 2) lines of code additional to the builder.Append() line.
I'm open to any other suggestions.
Create another method to do the appending like so:
CheckBeforeAppend(someFunction(someParameterAA, someParameterAB), builder)
CheckBeforeAppend(someFunction(someParameterBA, someParameterBB), builder)
....
Public Sub CheckBeforeAppend(s As String, sb As StringBuilder)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
A simple refactor such as this shortens your original code and means you don't need to duplicate the null or empty check on the return value of your function.
And for the extension method (place this code in a Module):
<Extension()>
Public Sub CheckBeforeAppend(s As String, sb As StringBuilder)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
usage:
someFunction(someParameterAA, someParameterAB).CheckBeforeAppend(sb)
or for an extension on StringBuilder:
<Extension()>
Public Sub CheckBeforeAppend(sb As StringBuilder, s As String)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
usage:
builder.CheckBeforeAppend(someFunction(someParameterAA, someParameterAB))
You can avoid calling the function twice by storing the result of the function in a variable.
Dim myString As String = someFunction(someParameterAA, someParameterAB)
If myString <> "" Then
builder.Append(myString).AppendLine()
End If
myString = someFunction(someParameterBA, someParameterBB)
If myString <> "" Then
builder.Append(myString).AppendLine()
End If
This way you just call the function once and check your conditions with the variable. Also the code looks a lot smaller and more understandable.

How to fill object variables defined in the dictionary based on JSON?

OK, that question sounds maybe a little confusing so I'll try to explain it with an example.
Pretend you have an object like this:
Class Something
Private varX As New Integer
Private varY As New String
'[..with the associated property definitions..]
Public Sub New()
End Sub
End Class
And another with:
Class JsonObject
Inherits Dictionary(Of String, String)
Public Function MakeObject() As Object 'or maybe even somethingObject
Dim somethingObject As New Something()
For Each kvp As KeyValuePair(Of String, String) In Me
'Here should happen something to use the Key as varX or varY and the Value as value for the varX or varY
somethingObject.CallByName(Me, kvp.Key, vbGet) = kpv.Value
Next
return somethingObject
End Function
End Class
I've got the 'CallByMe()' function from a previous question of myself
CallByName works different from the way you are trying to use it. Look at the documentation, it will tell you that in this particular case the correct usage would be
CallByName(Me, kvp.Key, vbSet, kpv.Value)
However, the function CallByName is part of a VB library that isn’t supported on all devices (notably it isn’t included in the .NET Mobile framework) and consequently it’s better not to use it.
Using proper reflection is slightly more complicated but guaranteed to work on all platforms.
Dim t = GetType(Something)
Dim field = t.GetField(kvp.Key, BindingFlags.NonPublic Or BindingFlags.Instance)
field.SetValue(Me, kvp.Value)

VB.NET If-Else in List

I just want to know if there's an approach in VB.NET that can find if a specific value exist on a list or something which can use in my If-else condition. What I'm doing now is to use this:
If ToStatus = "1CE" Or ToStatus = "2TL" Or ToStatus = "2PM" Then
'Do something
Else
'Do something
End If
This works fine, but how if I have hundreds of string to compare to ToStatus in the future? It will be a nightmare! Now, if such functionality exists, how can I add "And" and "Or" in the statement?
Thanks in advance!
You can use the Contains function:
Dim someList = New List(Of String) From { ... }
If Not someList.Contains(ToStatus) Then
You can do the following:
If {"1CE","2TL","2PM"}.Contains(ToStatus) Then
' ...
End If
Like Slaks pointed out, you can use Contains on an enumerable collection. But I think readability suffers. I don't care if some list contains my variable; I want to know if my variable is in some list.
You can wrap contains in an extension method like so:
Imports System.Runtime.CompilerServices
Module ExtensionMethods
<Extension()> _
Public Function [In](Of T)(ByVal item As T, ByVal ParamArray range() As T) As Boolean
Return range.Cast(Of T).Contains(item)
End Function
End Module
Then call like this:
If ToStatus.In("1CE","2TL","2PM") Then
you may use select case
select case A
case 5,6,7,8
msgbox "you are in"
case else
msgbox "you are excluded"
end select
For .NET 2.0
I came across another problem where SLaks solution won't work, that is if you use .NET 2.0 where method Contains is not present. So here's the solution:
If (Array.IndexOf(New String() {"1CE", "2TL", "2PM"}), ToStatus > -1) Then
'Do something if ToStatus is equal to any of the strings
Else
'Do something if ToStatus is not equal to any of the strings
End If
VB.NET - Alternative to Array.Contains?
Remove duplicates from the list
Dim ListWithoutDuplicates As New List(Of String)
For Each item As String In ListWithDuplicates
If ListWithoutDuplicates.Contains(item) Then
' string already in a list - do nothing
Else
ListWithoutDuplicates.Add(item)
End If
Next

How to write a simple Expression-like class in .NET 2.0?

I'm currently working in .NET 2.0 Visual Basic. The current project is an Active Directory Wrapper class library within which I have a Searcher(Of T) generic class that I wish to use to search the underlying directory for objects.
In this Searcher(Of T) class I have the following methods:
Private Function GetResults() As CustomSet(Of T)
Public Function ToList() As CustomSet(Of T)
Public Function Find(ByVal ParamArray filter() As Object) As CustomSet(Of T)
// And some other functions here...
The one that interests me the most is the Find() method to which I can pass property and values and would like to parse my LDAP query from this filter() ParamArray parameter. Actually, all I can figure out is this:
Public Sub SomeSub()
Dim groupSearcher As Searcher(Of Group) = New Searcher(Of Group)()
Dim groupsSet as CustomSet(Of Group) = groupSearcher.Find("Name=someName", "Description=someDescription")
// Working with the result here...
End Sub
But what I want to be able to offer to my users is this:
Public Sub SomeSub()
Dim groupSearcher As Searcher(Of Group) = New Searcher(Of Group)()
Dim groupsSet As CustomSet(Of Groupe) = groupSearcher.Find(Name = "someName", Guid = someGuid, Description = "someDescription")
// And work with the result here...
End Sub
In short, I want to offer some kind of Expression feature to my users, unless it is too much work, as this project is not the most important one and I don't have like 2 years to develop it. I think that the better thing I should do is to write something like CustomExpression that could be passed in parameters to some functions or subs.
Thanks for any suggestions that might bring me to my goal!
Interesting question. This is a language dependent feature, so I don't see this happening without some clever trickery of the IDE/compiler.
You could however have optional overloads on your Find method (vb.net is good for this), then make the search string manually to obtain the result.
Finally you could make use of lambda functions, but only in .net 3.5 and above. Even still, it would require your searcher to expose a preliminary set of data so you can recover the expression tree and build up the find string.
UPDATE
I've just been playing around with Reflection to see if I can retrieve the parameters passed, and build up a string dynamically depending on if they exist. This doesn't appear to be possible, due to the fact that compiled code doesn't reference the names.
This code just used was:
'-- Get all the "parameters"
Dim m As MethodInfo = GetType(Finder).GetMethod("Find")
Dim params() As ParameterInfo = m.GetParameters()
'-- We now have a reference to the parameter names, like Name and Description
Hmm. http://channel9.msdn.com/forums/TechOff/259443-Using-SystemReflection-to-obtain-parameter-values-dynamically/
Annoyingly it's not (easily) possible to recover the values sent, so we'll have to stick with building up the string in a non-dynamic fashion.
A simple optional method would look like:
Public Sub Find( _
Optional ByVal Name As String = "", _
Optional ByVal Description As String = "")
Dim query As String = String.Empty
If Not String.IsNullOrEmpty(Name) Then
query &= "Name=" & Name
'-- ..... more go here with your string seperater.
End If
End Sub