How to harness auto-completion of strings? - vb.net

I'm writing an application in which I have to pass strings as parameters. Like these:
GetValue("InternetGatewayDevice.DeviceInfo.Description")
GetValue("InternetGatewayDevice.DeviceInfo.HardwareVersion")
CheckValue("InternetGatewayDevice.DeviceInfo.Manufacturer")
ScrambleValue("InternetGatewayDevice.DeviceInfo.ModelName")
DeleteValue("InternetGatewayDevice.DeviceInfo.ProcessStatus.Process.1")
The full list is about 10500 entries, and i tought that i'd be really lost in searching if i misspell something.
So I am trying to declare a namespace for every string segment (separated by ".") and declare the last as a simple class that widens to a String of its FullName (except the base app namespace):
Class xconv
Public Shared Widening Operator CType(ByVal d As xconv) As String
Dim a As String = d.GetType.FullName
Dim b As New List(Of String)(Strings.Split(a, "."))
Dim c As String = Strings.Join(b.Skip(1).ToArray, ".")
Return c
End Operator
End Class
So I'd have these declarations:
Namespace InternetGatewayDevice
Namespace DeviceInfo
Class Description
Inherits xconv
End Class
End Namespace
End Namespace
This way IntelliSense is more than happy to autocomplete that string for me.
Now I'd have to do this for every possible string, so I opted (in order to retain my sanity) to make a method that does that:
Sub Create_Autocomlete_List()
Dim pathlist As New List(Of String)(IO.File.ReadAllLines("D:\list.txt"))
Dim def_list As New List(Of String)
Dim thedoc As String = ""
For Each kl As String In pathlist
Dim locdoc As String = ""
Dim el() As String = Strings.Split(kl, ".")
Dim elc As Integer = el.Length - 1
Dim elz As Integer = -1
Dim cdoc As String
For Each ol As String In el
elz += 1
If elz = elc Then
locdoc += "Class " + ol + vbCrLf + _
"Inherits xconv" + vbCrLf + _
"End Class"
Else
locdoc += "Namespace " + ol + vbCrLf
cdoc += vbCrLf + "End Namespace"
End If
Next
locdoc += cdoc
thedoc += locdoc + vbCrLf + vbCrLf
Next
IO.File.WriteAllText("D:\start_list_dot_net.txt", thedoc)
End Sub
The real problem is that this is HORRIBLY SLOW and memory-intense (now i dot a OutOfMemory Exception), and I have no idea on how Intellisense would perform with the (not available in the near future) output of the Create_Autocomlete_List() sub.
I believe that it would be very slow.
So the real questions are: Am I doing this right? Is there any better way to map a list of strings to auto-completable strings? Is there any "standard" way to do this?
What would you do in this case?

I don't know how Visual Studio is going to perform with thousands of classes, but your Create_Autocomlete_List method can be optimized to minimize memory usage by not storing everything in memory as you build the source code. This should also speed things up considerably.
It can also be simplified, since nested namespaces can be declared on one line, e.g. Namespace First.Second.Third.
Sub Create_Autocomlete_List()
Using output As StreamWriter = IO.File.CreateText("D:\start_list_dot_net.txt")
For Each line As String In IO.File.ReadLines("D:\list.txt")
Dim lastDotPos As Integer = line.LastIndexOf("."c)
Dim nsName As String = line.Substring(0, lastDotPos)
Dim clsName As String = line.Substring(lastDotPos + 1)
output.Write("Namespace ")
output.WriteLine(nsName)
output.Write(" Class ")
output.WriteLine(clsName)
output.WriteLine(" Inherits xconv")
output.WriteLine(" End Class")
output.WriteLine("End Namespace")
output.WriteLine()
Next
End Using
End Sub
Note the use of File.ReadLines instead of File.ReadAllLines, which returns an IEnumerable instead of an array. Also note that the output is written directly to the file, instead of being built in memory.
Note Based on your sample data, you may run into issues where the last node is not a valid class name. e.g. InternetGatewayDevice.DeviceInfo.ProcessStatus.Process.1 - 1 is not a valid class name in VB.NET. You will need to devise some mechanism to deal with this - maybe some unique prefix that you could strip in your widening operator.
I'm also not sure how usable the resulting classes will be, since presumably you would need to pass an instance to the methods:
GetValue(New InternetGatewayDevice.DeviceInfo.Description())
It seems like it would be nicer to have Shared strings on a class:
Namespace InternetGatewayDevice
Class DeviceInfo
Public Shared Description As String = "Description"
Public Shared HardwareVersion As String = "HardwareVersion"
' etc.
End Class
End Namespace
So you could just reference those strings:
GetValue(InternetGatewayDevice.DeviceInfo.Description)
However, I think that would be a lot harder to generate without creating name clashes due to the various levels of nesting.

Related

Count words in an external file using delimiter of a space

I want to calculate the number of words in a text file using a delimiter of a space (" "), however I am struggling.
Dim counter = 0
Dim delim = " "
Dim fields() As String
fields = Nothing
Dim line As String
line = Input
While (SR.EndOfStream)
line = SR.ReadLine()
End While
Console.WriteLine(vbLf & "Reading File.. ")
fields = line.Split(delim.ToCharArray())
For i = 0 To fields.Length
counter = counter + 1
Next
SR.Close()
Console.WriteLine(vbLf & "The word count is {0}", counter)
I do not know how to open the file and to get the do this, very confused; would like an explanation so I can edit and understand from it.
You're going to be reading a file as the source of the data, so let's create a variable to refer to its filename:
Dim srcFile = "C:\temp\twolines.txt"
As you have shown already, a variable is needed to hold the number of words found:
Dim counter = 0
To read from the file, a StreamReader will do the job. Now, we look at the documenation for it (yes, really) and notice that it has a Dispose method. That means that we have to explicitly dispose of it after we've used it to make sure that no system resources are tied up until the computer is next rebooted (e.g there could be a "memory leak"). Fortunately, there is the Using construct to take care of that for us:
Using sr As New StreamReader(srcFile)
And now we want to iterate over the content of the file line-by-line until the end of the file:
While Not sr.EndOfStream
Then we want to read a line and find how many items separated by spaces it has:
counter += sr.ReadLine().Split({" "c}, StringSplitOptions.RemoveEmptyEntries).Length
The += operator is like saying "add n to a" instead of saying "a = a + n". The {" "c} is a literal array of the character " "c. The c tells it that is a character and not a string of one character. The StringSplitOptions.RemoveEmptyEntries means that if there was text of "one two" then it would ignore the extra spaces.
So, if you were writing a console program, it might look like:
Imports System.IO
Module Module1
Sub Main()
Dim srcFile = "C:\temp\twolines.txt"
Dim counter = 0
Using sr As New StreamReader(srcFile)
While Not sr.EndOfStream
counter += sr.ReadLine().Split({" "c}, StringSplitOptions.RemoveEmptyEntries).Length
End While
End Using
Console.WriteLine(counter)
Console.ReadLine()
End Sub
End Module
Any embellishments such as writing out what the number represents or error checking are left up to you.
With Path.Combine you don't have to worry about where the slashes or back slashes go. You can get the path of special folders easily using the Environment class. The File class of System.IO is shared so you don't have to create an instance.
Public Sub Main()
Dim p As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Chapters.txt")
Debug.Print(Environment.SpecialFolder.MyDocuments.ToString)
Dim count As Integer = GetCount(p)
Console.WriteLine(count)
Console.ReadKey()
End Sub
Private Function GetCount(Path As String) As Integer
Dim s = File.ReadAllText(Path)
Return s.Split().Length
End Function
Use Split function, then Directly get the length of result array and add 1 to it.

string modifiers and properties do not work

I am trying to use Methods and Properties of the String Class to modify a string, but I keep getting an Invalid qualifier compile error. I even copied the following code* directly from the MSDN website and it throws the same error.
Public Sub Main()
Dim original As String
original = "aaabbb"
Dim modified As String
modified = original.Insert(3, " ")
End Sub
'This is the original code, but I had to change it slightly because the word-vba
'programming environment didn't like the syntax and highlighted everything red.
'Public Sub Main()
'Dim original As String = "aaabbb"
'Console.WriteLine("The original string: '{0}'", original)
'Dim modified As String = original.Insert(3, " ")
'Console.WriteLine("The modified string: '{0}'", modified)
'End Sub
Does word-vba not support string class modifiers and properties, am I not initializing the string correctly, or is there some other problem?
modified = original.Insert(3, " ")
You're thinking in VB.NET, but writing VBA. Strings (or any primitive or UDT type) don't have members in VBA. Not your fault, finding official VBA documentation is getting harder every day, with every "VBA" search yielding results for VB.NET.
That original code is clearly VB.NET.
If you mean to concatenate 3 spaces in front of original, then what you want to do is this:
modified = String(3, " ") & original
If you mean to get a new string in which a specified string is inserted at a specified index position in this instance (MSDN), then you want to do this (thanks #A.S.H!):
modified = Left$(original, 3) & " " & Right$(original, Len(original) - 3)

How to order list of files by file name with number?

I have a bunch of files in a directory that I am trying to get based off their type. Once I have them I would like to order them by file name (there is a number in them and I would like to order them that way)
My files returned are:
file-1.txt
file-2.txt
...
file-10.txt
file-11.txt
...
file-20.txt
But the order I get them in looks something more closely to this:
file-1.txt
file-10.txt
file-11.txt
...
file-2.txt
file-20.txt
Right now I am using Directory.GetFiles() and attempting to using the linq OrderBy property. However, I am failing pretty badly with what I would need to do to order my list of files like the first list above.
Directory.GetFiles() seems to be returning a list of strings so I am unable to get the list of file properties such as filename or name.
Here is my code currently:
documentPages = Directory.GetFiles(documentPath, "*.txt").OrderBy(Function(p) p).ToList()
Would anyone have any ideas?
It sounds like you might be looking for a "NaturalSort" - the kind of display File Explorer uses to order filenames containing numerals. For this you need a custom comparer:
Imports System.Runtime.InteropServices
Partial Class NativeMethods
<DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
Private Shared Function StrCmpLogicalW(s1 As String, s2 As String) As Int32
End Function
Friend Shared Function NaturalStringCompare(str1 As String, str2 As String) As Int32
Return StrCmpLogicalW(str1, str2)
End Function
End Class
Public Class NaturalStringComparer
Implements IComparer(Of String)
Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
Return NativeMethods.NaturalStringCompare(x, y)
End Function
End Class
Use it to sort the results you get:
Dim myComparer As New NaturalStringComparer
' OP post only shows the filename without path, so strip off path:
' (wont affect the result, just the display)
Dim files = Directory.EnumerateFiles(path_name_here).
Select(Function(s) Path.GetFileName(s)).ToList
Console.WriteLine("Before: {0}", String.Join(", ", files))
' sort the list using the Natural Comparer:
files.Sort(myComparer)
Console.WriteLine("After: {0}", String.Join(", ", files))
Results (one-lined to save space):
Before: file-1.txt, file-10.txt, file-11.txt, file-19.txt, file-2.txt, file-20.txt, file-3.txt, file-9.txt, file-99.txt
After: file-1.txt, file-2.txt, file-3.txt, file-9.txt, file-10.txt, file-11.txt, file-19.txt, file-20.txt, file-99.txt
One of the advantages of this is that it doesnt rely on a specific pattern or coding. It is more all-purpose and will handle more than one set of numbers in the text:
Game of Thrones\4 - A Feast For Crows\1 - Prologue.mp3
Game of Thrones\4 - A Feast For Crows\2 - The Prophet.mp3
...
Game of Thrones\4 - A Feast For Crows\10 - Brienne II.mp3
Game of Thrones\4 - A Feast For Crows\11 - Sansa.mp3
A Natural String Sort is so handy, is is something I personally dont mind polluting Intellisense with by creating an extension:
' List<string> version
<Extension>
Function ToNaturalSort(l As List(Of String)) As List(Of String)
l.Sort(New NaturalStringComparer())
Return l
End Function
' array version
<Extension>
Function ToNaturalSort(a As String()) As String()
Array.Sort(a, New NaturalStringComparer())
Return a
End Function
Usage now is even easier:
Dim files = Directory.EnumerateFiles(your_path).
Select(Function(s) Path.GetFileName(s)).
ToList.
ToNaturalSort()
' or without the path stripping:
Dim files = Directory.EnumerateFiles(your_path).ToList.ToNaturalSort()
I'm assuming the file and .txt parts are mutable, and just here as placeholders for file names and types that can vary.
I don't use regular expressions very often, so this may need some work yet, but it's definitely the direction you need to go:
Dim exp As String = "-([0-9]+)[.][^.]*$"
documentPages = Directory.GetFiles(documentPath, "*.txt").OrderBy(Function(p) Integer.Parse(Regex.Matches(p, exp)(0).Groups(1).Value)).ToList()
Looking again, I see I missed that you are filtering by *.txt files, which can help us narrow the expression:
Dim exp As String = "-([0-9]+)[.]txt$"
Another possible improvement brought by the other answer that includes test data is to allow for whitespace between the - and numerals:
Dim exp As String = "-[ ]*([0-9]+)[.]txt$"
It's further worth noting that the above will fail if there are text files that don't follow the pattern. We can account for that if needed:
Dim exp As String = "-[ ]*([0-9]+)[.][^.]*$"
Dim docs = Directory.GetFiles(documentPath, "*.txt")
documentPages = docs.OrderBy(
Function(p)
Dim matches As MatchCollection = Regex.Matches(p, exp)
If matches.Count = 0 OrElse matches(0).Groups.Count < 2 Then Return 0
Return Integer.Parse(matches(0).Groups(1).Value)
End Function).ToList()
You could also use Integer.MaxValue as your default option, depending on whether you want those to appear at the beginning or end of the list.

Converting Fixed length statement from VB6 to VB.Net

We perform a protocol based data sending to device where the device requires a formatted data packets.
the sample packet data is XXFSXXXFSXXXXXXXFSXXXXXX. The X mentioned is the max length size of each string. if data is less than string max length it should be filled with NULL character like ..11FS123FS1234XXXX (the remaining X will be filled with NULL).
I am just trying to convert one of VB6 function to VB.Net and below is the converted statement where i am facing issue
Option Strict Off
Option Explicit On
Imports Microsoft.VisualBasic.Compatibility.VB6
Imports System
Imports System.Runtime.InteropServices
Module FunctionCmd_Msg
Public FunCommand_Msg As Fun_CommandMessage = Fun_CommandMessage.CreateInstance()
'Function Command Message
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _ _
Public Structure Fun_CommandMessage
<VBFixedString(1)> Public one As String
<VBFixedString(1)> Public two As String
<VBFixedString(3)> Public three As String
Dim five As String
<VBFixedString(8)> Public four As String
Public Shared Function CreateInstance() As Fun_CommandMessage
Dim result As New Fun_CommandMessage
result.one = String.Empty
result.two = String.Empty
result.three = String.Empty
result.four = String.Empty
result.five = String.Empty
End Function
End Structure
End Module
assuming:
one = "1"
two = "1"
three = "123"
four = "5678"
five = "testing"
FS = character (field separator)
on concatenating the strings i need a fixed length string such like this:
one & two & FS & three & FS & five & FS & four
output: since four is a fixed length string of 8 length remaining 4 characters should be filled with null as below
11 FS 123 FS testing FS 5678XXXX
Fixed-length strings simply make no sense in .NET any more. Microsoft tried to provide a similar class for easier upgrade but the truth is that you should change your code depending on usage:
What did the fixed-length string do in your VB6 code? Was it for no good reason? Then use a normal String in .NET.
Was it for interop with a C API? Then use marshalling to set a size for an array in the C API call.
Just forget about the fixed length, and use regular vb.net strings. They will return fine to whatever calls that code, including interop.
So, just pad your strings, and you off to the races.
In fact, build a Msg class that does the dirty work for you.
This looks quite nice to me:
NOTE how I set this up that you ONLY define the length of the string in ONE place. (so I use len(m_string) to determine the length from THEN on in the code.
Also, for debug and this example, in place of vbcharNull (which you should use), I used X for testing.
Now, in your code?
Just use this:
Dim Msg As New MyMsg
With Msg
.one = "A"
.two = "B"
.three = "C"
.four = "D"
.Five = "E"
End With
Debug.WriteLine(Msg.Msg("*") & vbCrLf)
Debug.WriteLine("total lenght = " & Len(Msg.Msg("X").ToString))
Output:
A*B*CXX*EXXXXXXX*DXXXXXXX
total lenght = 25
I note in your code that you have FIVE before FOUR - but if that is what you want, then no problem
Note that the class ALWAYS maintains the lengths for you.
So just paste this code into your module or even a new separate class.
Public Class MyMsg
'Dim cPad As Char = vbNullChar
Dim cPad As Char = "X"
Private m_one As String = New String(cPad, 1)
Private m_two As String = New String(cPad, 1)
Private m_three As String = New String(cPad, 3)
Private m_four As String = New String(cPad, 8)
Private m_five As String = New String(cPad, 8)
Public Property one As String
Get
Return m_one
End Get
Set(value As String)
m_one = MyPad(value, m_one)
End Set
End Property
Public Property two As String
Get
Return m_two
End Get
Set(value As String)
m_two = MyPad(value, m_two)
End Set
End Property
Public Property three As String
Get
Return m_three
End Get
Set(value As String)
m_three = MyPad(value, m_three)
End Set
End Property
Public Property four As String
Get
Return m_four
End Get
Set(value As String)
m_four = MyPad(value, m_four)
End Set
End Property
Public Property Five As String
Get
Return m_five
End Get
Set(value As String)
m_five = MyPad(value, m_five)
End Set
End Property
Public Function Msg(FS As String) As String
Return m_one & FS & m_two & FS & m_three & FS & m_five & FS & m_four
End Function
Private Function MyPad(str As String, strV As String) As String
Return Strings.Left(str & New String(Me.cPad, Len(strV)), Len(strV))
End Function
End Class
As noted, change the commented out line of "X" for the char back to vbCharNull.
And of course you STILL get to choose the delimiter. I used
Msg.Msg("*")
so I used a "*", but you can use space, or anything you want.

how to input data into an array from a text file that are vbTab separated?

I am having trouble turning a set of data from a .txt file into arrays, basically, what i have in the text file is:
Eddy vbtab 20
Andy vbtab 30
James vbtab 20
etc..
I want to set up the names as a Names array, and numbers as number array.
Now what I have done is
strFilename = "CustomerPrices.txt"
If File.Exists(strFilename) Then
Dim srReader As New StreamReader(strFilename)
intRecords = srReader.ReadLine()
intRows = intRecords
For i = 0 To intRows - 1
intLastBlank = strInput.IndexOf(vbTab)
strName(intPrices) = strInput.Substring(0, intLastBlank)
dblPrices(intPrices) = Double.Parse(strInput.Substring(intLastBlank + 1))
But when I debug I get a problem "Object Reference not set to an instance of an object"
Can anyone give me some advise?
Thanks
Separate arrays are probably a bad idea here. They group your data by fields, when it's almost always better to group your data by records. What you want instead is a single collection filled with classes of a particular type. Go for something like this:
Public Class CustomerPrice
Public Property Name As String
Public Property Price As Decimal
End Class
Public Function ReadCustomerPrices(ByVal fileName As String) As List(Of CustomerPrice)
Dim result As New List(Of CustomerPrice)()
Using srReader As New StreamReader(fileName)
Dim line As String
While (line = srReader.ReadLine()) <> Nothing
Dim data() As String = line.Split(vbTab)
result.Add(new CustomerPrice() From {Name = data(0), Price = Decimal.Parse(data(1))})
End While
End Using
Return result
End Function
Some other things worth noting in this code:
The Using block will guarantee the file is closed, even if an exception is thrown
It's almost never appropriate to check File.Exists(). It's wasteful code, because you still have to be able to handle the file io exceptions.
When working with money, you pretty much always want to use the Decimal type rather than Double
This code requires Visual Studio 2010 / .Net 4, and was typed directly into the reply window and so likely contains a bug, or even base syntax error.