Convert a value with a regex to a real value - vbscript

I have a huge file containing values with a regex in it, like this:
LGP0041_\d{4}\.dta
objd135a_\S{3}.txt
Now I need to convert these to a valid example value, like this:
LGP0041_1234.dta
objd135a_abc.txt
I know of the RegExp object to check if there is a match, but is there also a way to create valid values?

A regular grammar can be used to recognize or produce words of its language, but the VBScript regexp engine does not implement producing. So you have to roll your own.
Your sample does not contain contain regular patterns. \S can't mean 'non-whitespace' because you won't like characters illegal in a file name and a representative sample of file names should contain elements with spaces. The fact that the first sample escapes the extension dot and the second one doesn't makes me think that your syntax specs aren't really thought out. If you come up with a (regular) grammar of your inputs, I'm willing to give your problem further thought.
Some code to base the thinking on:
Option Explicit
Function rndInt(lowerbound, upperbound)
rndInt = Int((upperbound - lowerbound + 1) * Rnd + lowerbound)
End Function
Sub shuffleAD(aX)
' Durstenfeld's Permutation Algorithm
Dim J, K, Temp
For J = UBound(aX) To 1 Step -1
K = Int((J + 1) * Rnd) ' random number 0 - J
Temp = aX(J)
aX(J) = aX(K)
aX(K) = Temp
Next
End Sub ' shuffleAD
Class cRGen
Private m_
Public Function init(s)
Set init = Me
ReDim m_(Len(s) - 1)
Dim i
For i = 0 To UBound(m_)
m_(i) = Mid(s, i + 1, 1)
Next
End Function
Public Function getNext(mi, ma)
shuffleAD m_
getNext = Mid(Join(m_, ""), 1, rndInt(mi, ma))
End Function
End Class
Dim goRpl : Set goRpl = Nothing
Function magic(m, w, mi, ma, p, src)
If IsEmpty(ma) Then ma = mi
magic = goRpl.m_dicGens(w).getNext(mi, ma)
End Function
Class cRpl
Private m_fRpl
Private m_r
Public m_dicGens
Private Sub Class_Initialize()
Set m_fRpl = GetRef("magic")
Set m_r = New RegExp
m_r.Pattern = "\\(\w){(\d+)(?:,(\d+))?}"
Set m_dicGens = CreateObject("Scripting.Dictionary")
Set m_dicGens("d") = New cRGen.init("0123456789")
Set m_dicGens("S") = New cRGen.init("abcdefghij")
End Sub
Public Function rpl(s)
Set goRpl = me
rpl = m_r.Replace(s, m_fRpl)
End Function
End Class
Randomize
Dim aTests : aTests = Array( _
"LGP0041_\d{4}.dta" _
, "objd135a_\S{3}.txt" _
, "x_\S{3,8}.txt" _
)
Dim oRpl : Set oRpl = New cRpl
Dim sTest
For Each sTest In aTests
WScript.Echo sTest, "=>", oRpl.rpl(sTest)
Next
output:
cscript 28936688.vbs
LGP0041_\d{4}.dta => LGP0041_4317.dta
objd135a_\S{3}.txt => objd135a_cea.txt
x_\S{3,8}.txt => x_jgcfidh.txt
cscript 28936688.vbs
LGP0041_\d{4}.dta => LGP0041_8054.dta
objd135a_\S{3}.txt => objd135a_eci.txt
x_\S{3,8}.txt => x_ahfgd.txt
This should at least identify the needed components:
generators that deliver strings of specific character sets
a (sorry: global) replace function that maps 'type letters' to generators, handles 'width specs', and build the output
a regular pattern to parse the specs from your inputs

Related

Visual Basic split function

Ok so now I'm not getting an error but instead of everything being posted in the listbox it only contains the first line of text from the .txt file. This is the code I changed it too:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim inFile As IO.StreamReader
If IO.File.Exists("StudentList.txt") = True Then
inFile = IO.File.OpenText("StudentList.txt")
For index As Integer = 0 To inFile.Peek = -1
Dim splits = inFile.ReadLine.Split(","c)
Member(index).ID = splits(0)
Member(index).lastName = splits(1)
Member(index).firstName = splits(2)
Member(index).middleName = splits(3)
Member(index).grade = splits(4)
Member(index).period = splits(5)
ListBox1.Items.Add(Member(index).ID.PadLeft(10) & " " & Member(index).lastName & " " & Member(index).firstName)
Next
inFile.Close()
Else
MessageBox.Show("error", "error", MessageBoxButtons.OK)
End If
End Sub
The problem is that you're trying to assign a string array to a Member. That is, you have:
Member(index) = infile.ReadLine.Split(",", c);
You need to assign each field:
Dim splits = infile.ReadLine.Split(",", c);
Member(index).ID = splits(0);
Member(index).lastName = splits(1);
... etc.
Update after OP edit
I suspect the problem now is that your For loop is executing only once, or index isn't being incremented. I don't know where you came up with that wonky infile.Peek = -1 thing, but I suspect it doesn't work the way you think it does. Use something more conventional, like this.
Dim index As Integer = 0
For Each line As String In File.ReadLines("StudentList.txt")
Dim splits = line.Split(",", c)
Member(index).ID = splits(0)
' etc.
ListBox1.Add(...)
Index = Index + 1
Next

Converting characters to numbers

In a program I am currently making, I need to convert a certain set of characters to numbers. This set of characters is prone to change and also includes CaPiTaL letters in the conversion, of which the capitals will be converted to a different number as with special characters (such as "#", "&", "$", etc). The numbers follow a simple pattern; a = 1, b = 2, c = 3, etc.
The current method I am using to do this is using a separate:
If letter = "a" then
number = 1
End If
This method however is a chore to write up and also seems to be a bit inefficient (in terms of running continuous/bulk amounts of letters through it). Is there any function, sub, etc. that can be used to perform an action like this in VBScript (specifically in VB Express 2010)?
When in doubt, read the documentation. The Asc() function returns the character code of a given ASCII character:
>>> WScript.Echo Asc("a")
97
>>> WScript.Echo Asc("A")
65
Another option is to create a dictionary with the desired mappings:
Set map = CreateObject("Scripting.Dictionary")
map.Add "a", 1
map.Add "b", 2
...
which can be used like this:
>>> WScript.Echo map("a")
1
>>> WScript.Echo map("b")
2
Step 0: Using positional mapping:
Option Explicit
' Start with a function working on a string that calls
' a character decode function
Function decodeS(s)
ReDim aTmp(Len(s) - 1)
Dim i
For i = 0 To UBound(aTmp)
aTmp(i) = decodeC(Mid(s, i + 1, 1))
Next
decodeS = aTmp
End Function
' naive decode via InStr/Index/Position
Function decodeC(c)
decodeC = InStr("abc", c)
End Function
WScript.Echo "acbbca", Join(decodeS("acbbca"))
Step 1: guarded positional mapping:
' guarded decode via InStr/Index/Position
Function decodeC(c)
decodeC = -1
Dim p : p = InStr("abcdefghiA", c)
If p Then decodeC = p
End Function
WScript.Echo "acbbcAx", Join(decodeS("acbbcAx"))
Step 2: positional mapping 'doesn't work', switch to lookup:
' decode via parallel array and InStr/Index/Position
Dim gsC : gsC = "aAbBcC"
Dim gaC : gaC = Split("-1 1 10 2 20 3 30")
Function decodeC(c)
decodeC = CLng(gaC(InStr(gsC, c)))
End Function
WScript.Echo "CcBxbAa", Join(decodeS("CcBxbAa"))
Step 3: you prefer dictionary lookup:
' decode via dictionary
Dim gdC : Set gdC = CreateObject("Scripting.Dictionary")
gdC("a") = 1
gdC("A") = 10
Function decodeC(c)
decodeC = -1
If gdC.Exists(c) Then decodeC = gdC(c)
End Function
WScript.Echo "CcBxbAa", Join(decodeS("CcBxbAa"))

Excel copy/sort data while counting/removing duplicates

Ok so I've searched and searched and can't quite find what I'm looking for.
I have a workbook and what I'm basically trying to do is take the entries from certain ranges (Sheet1 - E4:E12, E14:E20, I4:I7, I9:I12, I14:I17, & I19:I21) and put them in a separate list on Sheet2. I then want the new list on Sheet2 to be sorted by how many times an entry appeared on Sheet1 as well as display the amount.
example http://demonik.doomdns.com/images/excel.png
Obviously as can be seen by the ranges I listed above, this sample is much smaller lol, was just having trouble trying to figure out how to describe everything and figured an image would help.
Basically I am trying to use VBA (the update would be initialized by hitting a button) to copy data from Sheet1 and put all the ranges into one list in Sheet2 that is sorted by how many times it appeared on Sheet1, and then alphabetically.
If a better discription is needed just comment and let me know, I've always been horrible at trying to describe stuff like this lol.
Thanks in advance!
Another detail: I cant have it search for specific things as the data in the ranges on Sheet1 may change. Everything must be dynamic.
I started out with this data
and used the following code to read it into an array, sort the array, and count the duplicate values, then output the result to sheet2
Sub Example()
Dim vCell As Range
Dim vRng() As Variant
Dim i As Integer
ReDim vRng(0 To 0) As Variant
Sheets("Sheet2").Cells.Delete
Sheets("Sheet1").Select
For Each vCell In ActiveSheet.UsedRange
If vCell.Value <> "" Then
ReDim Preserve vRng(0 To i) As Variant
vRng(i) = vCell.Value
i = i + 1
End If
Next
vRng = CountDuplicates(vRng)
Sheets("Sheet2").Select
Range(Cells(1, 1), Cells(UBound(vRng), UBound(vRng, 2))) = vRng
Rows(1).Insert
Range("A1:B1") = Array("Entry", "Times Entered")
ActiveSheet.UsedRange.Sort Range("B1"), xlDescending
End Sub
Function CountDuplicates(List() As Variant) As Variant()
Dim CurVal As String
Dim NxtVal As String
Dim DupCnt As Integer
Dim Result() As Variant
Dim i As Integer
Dim x As Integer
ReDim Result(1 To 2, 0 To 0) As Variant
List = SortAZ(List)
For i = 0 To UBound(List)
CurVal = List(i)
If i = UBound(List) Then
NxtVal = ""
Else
NxtVal = List(i + 1)
End If
If CurVal = NxtVal Then
DupCnt = DupCnt + 1
Else
DupCnt = DupCnt + 1
ReDim Preserve Result(1 To 2, 0 To x) As Variant
Result(1, x) = CurVal
Result(2, x) = DupCnt
x = x + 1
DupCnt = 0
End If
Next
Result = WorksheetFunction.Transpose(Result)
CountDuplicates = Result
End Function
Function SortAZ(MyArray() As Variant) As Variant()
Dim First As Integer
Dim Last As Integer
Dim i As Integer
Dim x As Integer
Dim Temp As String
First = LBound(MyArray)
Last = UBound(MyArray)
For i = First To Last - 1
For x = i + 1 To Last
If MyArray(i) > MyArray(x) Then
Temp = MyArray(x)
MyArray(x) = MyArray(i)
MyArray(i) = Temp
End If
Next
Next
SortAZ = MyArray
End Function
End Result:
Here is a possible solution that I have started for you. What you are asking to be done gets rather complicated. Here is what I have so far:
Option Explicit
Sub test()
Dim items() As String
Dim itemCount() As String
Dim currCell As Range
Dim currString As String
Dim inArr As Boolean
Dim arrLength As Integer
Dim iterator As Integer
Dim x As Integer
Dim fullRange As Range
Set fullRange = Range("E1:E15")
iterator = 0
For Each cell In fullRange 'cycle through the range that has the values
inArr = False
For Each currString In items 'cycle through all values in array, if
'values is found in array, then inArr is set to true
If currCell.Value = currString Then 'if the value in the cell we
'are currently checking is in the array, then set inArr to true
inArr = True
End If
Next
If inArr = False Then 'if we did not find the value in the array
arrLength = arrLength + 1
ReDim Preserve items(arrLength) 'resize the array to fit the new values
items(iterator) = currCell.Value 'add the value to the array
iterator = iterator + 1
End If
Next
'This where it gets tricky. Now that you have all unique values in the array,
'you will need to count how many times each value is in the range.
'You can either make another array to hold those values or you can
'put those counts on the sheet somewhere to store them and access them later.
'This is tough stuff! It is not easy what you need to be done.
For x = 1 To UBound(items)
Next
End Sub
All that this does so far is get unique values into the array so that you can count how many times each one is in the range.

How to Remove Specific Special Characters

I have a string like X5BC8373XXX. Where X = a special character equals a Square.
I also have some special characters like \n but I remove them, but I can't remove the squares...
I'd like to know how to remove it.
I Found this method:
Dim Test As String
Test = Replace(Mscomm1.Input, Chr(160), Chr(64) 'Here I remove some of the special characters like \n
Test = Left$(Test, Len(Test) -2)
Test = Right$(Test, Len(Test) -2)
This method DOES remove those special characters, but it's also removing my first character 5.
I realize that this method just remove 2 characters from the left and the right,
but how could I work around this to remove these special characters ?
Also I saw something with vblF, CtrlF something like this, but I couldn't work with this ;\
You can use regular expressions. If you want to remove everything that's not a number or letter, you can use the code below. If there are other characters you want to keep, regular expressions are highly customizable, but can get a little confusing.
This also has the benefit of doing the whole string at once, instead of character by character.
You'll need to reference Microsoft VBScript Regular Expressions in your project.
Function AlphaNum(OldString As String)
Dim RE As New RegExp
RE.Pattern = "[^A-Za-z0-9]"
RE.Global = True
AlphaNum = RE.Replace(OldString, "")
End Function
Cleaning out non-printable characters is easy enough. One brute-force but easily customizable method might be:
Private Function Printable(ByVal Text As String) As String
Dim I As Long
Dim Char As String
Dim Count As Long
Printable = Text 'Allocate space, same width as original.
For I = 1 To Len(Text)
Char = Mid$(Text, I, 1)
If Char Like "[ -~]" Then
'Char was in the range " " through "~" so keep it.
Count = Count + 1
Mid$(Printable, Count, 1) = Char
End If
Next
Printable = Left$(Printable, Count)
End Function
Private Sub Test()
Dim S As String
S = vbVerticalTab & "ABC" & vbFormFeed & vbBack
Text1.Text = S 'Shows "boxes" or "?" depending on the font.
Text2.Text = Printable(S)
End Sub
This will remove control characters (below CHR(32))
Function CleanString(strBefore As String) As String
CleanString = ""
Dim strAfter As String
Dim intAscii As Integer
Dim strTest As String
Dim dblX As Double
Dim dblLen As Double
intLen = Len(strBefore)
For dblX = 1 To dblLen
strTest = Mid(strBefore, dblX, 1)
If Asc(strTest) < 32 Then
strTest = " "
End If
strAfter = strAfter & strTest
Next dblX
CleanString = strAfter
End Function

excel vba worksheetfunction.sum

I have a function which should calculate the sum in an array of cells. This function is called in another subroutine very often and therefore has to be real fast. First I used a for-loop to get the numbers but it was too slow. Then I wanted to use a worksheetfunction but this is not working for reasons unknown. Here is the code of the function:
Function CalcMD(rownumbers, colnumber)
Dim MMDRow As Integer
Dim SearchRange As String
Dim FirstAddress As Boolean
MMDRow = MMDRow()
FirstAddress = False
SearchRange = ""
'building the search range
For i = 0 To UBound(rownumbers)
If rownumbers(i) > 0 And rownumbers(i) < MMDRow Then
If FirstAddress = False Then
SearchRange = SearchRange & cells(rownumbers(i), colnumber).Address(False, False)
FirstAddress = True
Else
SearchRange = SearchRange & ";" & cells(rownumbers(i), colnumber).Address(False, False)
End If
End If
Next
CalcMD = WorksheetFunction.Sum(Range(SearchRange))
End Function
Is there a better/faster way to get this result?
Here is a try. The trickiest part is finding the last row you want to sum, I think this is where we should work if this first attempt doesn't solve your case.
Function CalcMD(rownumbers, colnumber)
Dim MMDRow As Integer, iMaxRow As Integer
Dim SearchRange As String
MMDRow = MMDRow()
'Find the last row where you want to sum the data
iMaxRow = WorksheetFunction.Min(MMDRow - 1, WorksheetFunction.Max(rownumbers))
CalcMD = WorksheetFunction.Sum(Cells(iMaxRow, colnumber))
End Function
Please also note that there aren't many error checking here (especially to check the input data)
Using JMax's approach:
Function CalcMD(rownumbers, colnumber)
Dim MMDRow As Integer, iMaxRow As Integer
Dim SearchRange As String
MMDRow = MMDRow()
'Find the last row where you want to sum the data
iMaxRow = WorksheetFunction.Min(MMDRow - 1, WorksheetFunction.Max(rownumbers))
CalcMD = WorksheetFunction.SumIF(Cells(iMaxRow, colnumber),">0")
End Function
Is that what you are looking for?

Resources