MS Access VBA Substitution Cipher Encrypt/Decrypt - ms-access

Could anyone suggest please how I can achieve a substitution cipher style; encrypt and decrypt function in VBA. I appreciate hashing is considered the better way but I need reversible encryption. Many Thanks.

You can use Blowfish. There's a Visual Basic 6 version that will work in Access, available here:
http://www.di-mgt.com.au/cryptoBlowfishVer6.html
You can also try TwoFish.

There is a simple example here or you can use the even simpler ROT13 cipher.
These are useful for obscuring a little text, but I'd not use them for anything that actually needs to be kept secure.

Many thanks for all the answers provided in reference to my question, it's good to see there are different approaches, this is one I coded yesterday morning. It allows a different cipher keyword/phrase to be used for both Upper & Lowercase letters, I have used 'Zebras' in this example, it also runs a second pass with the ROT13 cipher. Encryption method:
Public Function Encrypt(strvalue As String) As String
Const LowerAlpha As String = "abcdefghijklmnopqrstuvwxyz"
Const LowerSub As String = "zebrascdfghijklmnopqtuvwxy" 'zebras
Const UpperAlpha As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Const UpperSub As String = "ZEBRASCDFGHIJKLMNOPQTUVWXY" 'ZEBRAS
Dim lngi As Long
Dim lngE As Long
Dim strEncrypt As String
Dim strLetter As String
If strvalue & "" = "" Then Exit Function
For lngi = 1 To Len(strvalue)
strLetter = Mid(strvalue, lngi, 1)
Select Case Asc(strLetter)
Case 65 To 90 'Uppercase
'Find position in alpha string
For lngE = 1 To Len(UpperAlpha)
If Mid(UpperAlpha, lngE, 1) = strLetter Then GoTo USub
Next
USub:
strEncrypt = strEncrypt & Mid(UpperSub, lngE, 1)
Case 97 To 122 'Lowercase
'Find position in alpha string
For lngE = 1 To Len(LowerAlpha)
If Mid(LowerAlpha, lngE, 1) = strLetter Then GoTo LSub
Next
LSub:
strEncrypt = strEncrypt & Mid(LowerSub, lngE, 1)
Case Else 'Do not substitute
strEncrypt = strEncrypt & strLetter
End Select
Next
'Now pass this string through ROT13 for another tier of security
For lngi = 1 To Len(strEncrypt)
Encrypt = Encrypt & Chr(Asc(Mid(strEncrypt, lngi, 1)) + 13)
Next
End Function
And this is the Decryption that goes with it:
Public Function Decrypt(strvalue As String) As String
Const LowerAlpha As String = "abcdefghijklmnopqrstuvwxyz"
Const LowerSub As String = "zebrascdfghijklmnopqtuvwxy" 'zebras
Const UpperAlpha As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Const UpperSub As String = "ZEBRASCDFGHIJKLMNOPQTUVWXY" 'ZEBRAS
Dim lngi As Long
Dim lngE As Long
Dim strDecrypt As String
Dim strLetter As String
If strvalue & "" = "" Then Exit Function
'Reverse the ROT13 cipher
For lngi = 1 To Len(strvalue)
strDecrypt = strDecrypt & Chr(Asc(Mid(strvalue, lngi, 1)) - 13)
Next
'Now reverse the encryption
For lngi = 1 To Len(strDecrypt)
strLetter = Mid(strDecrypt, lngi, 1)
Select Case Asc(strLetter)
Case 65 To 90 'Uppercase
'Find position in sub string
For lngE = 1 To Len(UpperSub)
If Mid(UpperSub, lngE, 1) = strLetter Then GoTo USub
Next
USub:
Decrypt = Decrypt & Mid(UpperAlpha, lngE, 1)
Case 97 To 122 'Lowercase
'Find position in sub string
For lngE = 1 To Len(LowerSub)
If Mid(LowerSub, lngE, 1) = strLetter Then GoTo LSub
Next
LSub:
Decrypt = Decrypt & Mid(LowerAlpha, lngE, 1)
Case Else 'Do not substitute
Decrypt = Decrypt & strLetter
End Select
Next
End Function
I hope the coding is very simple to follow for those who do not have vast experience with VBA coding and it can be lifted straight from the page; but again thanks for all the other answers.

Related

Access Vba - Reverse a string's contents

I have a string called str = "12345-5, 12345-4, 12345-3, 12345-2, 12345-1 I need to reverse the string so it looks like this str = "12345-1, 12345-2, 12345-3, 12345-4, 12345-5"
I have tried the strReverse method, and it almost did what I wanted...
Sub rev()
Dim str As String
str = "12345-5, 12345-4, 12345-3, 12345-2, 12345-1"
str = StrReverse(Trim(str))
'turns out to be str = "1-54321 ,2-54321 ,3-54321 ,4-54321 ,5-54321"
End Sub
but it ended up reversing the whole string, should have guessed that. So I'm wondering should I use a regex expression to parse the string and remove the "12345-" and then reverse it and add it back in? I'm not too sure if that would be the best method for my problem. Does anyone know a solution to my problem or could point me in the right direction? Thanks
Use Split then loop backwards through the array:
Sub rev()
Dim str As String
str = "12345-5, 12345-4, 12345-3, 12345-2, 12345-1"
Dim strArr() As String
strArr = Split(str, ",")
str = ""
Dim i As Long
For i = UBound(strArr) To LBound(strArr) Step -1
str = str & ", " & Trim(strArr(i))
Next i
str = Mid(str, 3)
Debug.Print str
End Sub
I would do it like this:
Sub TestMe()
Dim str As String
str = "12345-5, 12345-4, 12345-3, 12345-2, 12345-1"
str = StrReverse(str)
Dim myArr As Variant
myArr = Split(str, ",")
Dim newString As String
Dim myA As Variant
For Each myA In myArr
newString = newString & StrReverse(myA) & ","
Next myA
newString = Trim(Left(newString, Len(newString) - 1))
Debug.Print newString
End Sub
Getting this:
12345-1, 12345-2, 12345-3, 12345-4,12345-5
In general, this is quite popular Algorithmic problem, which used to be asked by Google for Junior Developers. Sounding like this - Efficiently reverse the order of the words (not characters) in an array of characters

libre office macro find replace formatted text

I want to go through a document and find all center aligned text and delete it, I can setup formatted text on the find and replace tool, but when I record, it doesn't save formatting... does anyone know how to edit the basic code to do this?
also is the open office documentation compatible with libre office.
Recording in OpenOffice generates dispatcher code, which usually isn't very good. It's better to use the UNO API when writing macros. Here is some code that does what you want:
Sub DeleteCenteredLines
oDoc = ThisComponent
Dim vDescriptor, vFound
' Create a descriptor from a searchable document.
vDescriptor = oDoc.createSearchDescriptor()
' Set the text for which to search and other
With vDescriptor
.searchString = ""
.searchAll=True
End With
Dim srchAttributes(0) As New com.sun.star.beans.PropertyValue
srchAttributes(0).Name = "ParaAdjust"
srchAttributes(0).Value = com.sun.star.style.ParagraphAdjust.CENTER
vDescriptor.SetSearchAttributes(srchAttributes())
' Find the first one
vFound = oDoc.findFirst(vDescriptor)
Do While Not IsNull(vFound)
vFound.setPropertyValue("ParaAdjust", com.sun.star.style.ParagraphAdjust.LEFT)
oTC = oDoc.Text.createTextCursorByRange(vFound)
oTC.gotoStartOfParagraph(false)
oTC.gotoEndOfParagraph(true)
oTC.String = ""
oTC.goRight(1,true)
oTC.String = ""
vFound = oDoc.findNext( vFound.End, vDescriptor)
Loop
End Sub
Check out http://www.pitonyak.org/AndrewMacro.odt for examples of many common tasks. In my experience, looking for examples in this document is usually easier than trying to record macros and make sense of what was recorded.
This works for OpenOffice as well as LibreOffice. Generally the API is the same for both.
My solution which replaces strings in italic and superscript to tags.
(it is extremly slow. Maybe someone can improve it)
Sub replace_italico_sobrescrito_por_tag()
MsgBox "It takes long to run."
Dim vartemp As String
theDoc = thisComponent
iSheetsCount = theDoc.Sheets.Count
Dim theCell As Object, rText As String, textSlice As String, textItalic As Long, textSup As Integer
Dim theParEnum As Object, theParElement As Object
Dim theSubEnum As Object, theSubElement As Object
For k=0 to iSheetsCount-1
Sheet = theDoc.getSheets().getByIndex(k)
dim pX as integer, pY as integer, maxcol as integer, maxrow as integer
maxcol = 100
maxrow = 500
For pX=0 to maxrow
For pY=0 to maxcol
theCell = Sheet.GetCellByPosition(pX, pY)
theParEnum = theCell.GetText().CreateEnumeration
rText = ""
Do While theParEnum.HasMoreElements
theParElement = theParEnum.NextElement
theSubEnum = theParElement.CreateEnumeration
Do While theSubEnum.HasMoreElements
textSlice = ""
theSubElement = theSubEnum.NextElement
If theCell.Type = 2 Then
textSlice = theSubElement.String
textItalic = theSubElement.CharPosture
textSup = theSubElement.CharEscapement
Else
textSlice = theCell.String
End If
If theSubElement.CharPosture >= 1 Then
textSlice = "<i>" & textSlice & "</i>"
End If
If theSubElement.CharEscapement > 0 Then
textSlice = "<sup>" & textSlice & "</sup>"
End If
rText = rText & textSlice
Loop
Loop
theCell.String=rText
Next pY
Next pX
Next k
MsgBox "End"
End Sub

How to export a temporary recordset to a csv file using vba

I have a ms access table that is tracking 50 products with their daily sold volumes. I would like to export using vba 1 csv file (including headers) for each product showing the daily volumes from a recordset without saving the recordset to a permanent query. I am using the below code but I am stuck at the point of the actual export highlighted below in code.
Any assistance in fixing this is appreciated.
Dim rst As Recordset
Dim rstId As Recordset
SQLExportIds = "SELECT DISTINCT tblDailyVols.SecId FROM tblDailyVols WHERE tblDailyVols.IsDeleted=False"
Set rstId = CurrentDb.OpenRecordset(SQLExportIds)
If rstId.EOF = True Then
MsgBox "No Products Found"
Exit Sub
End If
Do While rstId.EOF = False
SecId = rstId.Fields("SecId")
SQLExportQuotes = " SELECT tblDailyVols.ID , tblDailyVols.TradedVolume, tblDailyVols.EffectiveDate FROM tblDailyVols "
SQLExportQuotes = SQLExportQuotes & " WHERE tblDailyVols.IsDeleted=False and tblDailyVols.ID = " & SecId
SQLExportQuotes = SQLExportQuotes & " ORDER BY tblDailyVols.EffectiveDate "
Set rst = CurrentDb.OpenRecordset(SQLExportQuotes)
If rst.EOF = True Then
MsgBox "No Quotes Found"
Exit Sub
End If
IDFound = rst.Fields("ID")
OutputPlace = “C:\Output” & IDFound & ".csv"
Set qdfTemp = CurrentDb.CreateQueryDef("", SQLExportQuotes)
**DoCmd.TransferText acExportDelim, , 1, OutputPlace, True** <--This Here Line Fails
Set rst = Nothing
rstId.MoveNext
Loop
Set rstId = Nothing
You will have to create an actual named QueryDef object for TransferText to work with, but then you can just delete it afterwards. Something like this:
Set qdfTemp = CurrentDb.CreateQueryDef("zzzTemp", SQLExportQuotes)
Set qdfTemp = Nothing
DoCmd.TransferText acExportDelim, , "zzzTemp", OutputPlace, True
DoCmd.DeleteObject acQuery, "zzzTemp"
You asked for a VBA solution, and I detect a preference for not creating new Access objects; you may well have good reasons for that, but the 'pure' VBA solution is a lot of work.
A solution that implements encapsulating text fields in quotes is the bare minimum for a competent answer. After that, you need to address the three big issues:
Optimising away VBA's clunky string-handling;
The Byte Order Marker, which VBA embeds in every string it saves to
file, ensuring that some of the most common consumers of a csv file
cannot read it properly;
...And there's rarely any middle ground between writing the file
line-by-line, forever, and writing it in one big chunk that'll throw
an out-of-memory error on larger recordsets.
Beginners in VBA may find the string-optimisations difficult to understand: the biggest performance gain available in native VBA is to avoid string allocation and concatenation ( here's why: http://www.aivosto.com/vbtips/stringopt2.html#huge ) - so I use join, split, and replace instead of myString = MyString & MoreString
The trailing loop, with the RecordSet.GetRows() call at the very end, will raise eyebrows among coders with strong opinions about structured programming: but there are constraints on how you can order the code so that the 'chunks' are concatenated into the file without any missed bytes, out-of-register shifts in the byte order, or blank lines.
So here goes:
Public Function RecordsetToCSV(ByRef rst As ADODB.Recordset, _
ByRef OutputFile As String, _
Optional ByRef FieldList As Variant, _
Optional ByVal CoerceText As Boolean = True, _
Optional ByVal CleanupText As Boolean = True _
) As Long
' Output a recordset to a csv file and returns the row count.
' If the output file is locked, or specified in an inaccessible location, the
' 'ByRef' OutputFile parameter becomes a file in the user's local temp folder
' You can supply your own field list. This isn't a substituted file header of
' aliased field names: it is a subset of the field names, which ADO will read
' selectively from the recordset. Each item in the list matches a named field
' CoerceText=TRUE will encapsulate all items, numeric or not, in quote marks.
' CleanupText=TRUE strips quotes and linefeeds from the data: FALSE is faster
' You should only set them FALSE if you're confident that the data is 'clean'
' with no quote marks, commas or line breaks in any unencapsulated text field
' This code handles unicode, and outputs a file that can be read by Microsoft
' ODBC and OLEDB database drivers by removing the Byte Order Marker.
On Error Resume Next
' Coding note: we're not doing any string-handling in VBA.Strings: allocating
' deallocating and (especially!) concatenating are SLOW. We are using the VBA
' Join and Split functions ONLY. Feel free to optimise further by declaring a
' faster set of string functions from the Kernel if you want to.
'
' Other optimisations: type pun. Byte Arrays are interchangeable with strings
' Some of our loops through these arrays have a 'step' of 2. This optimises a
' search-and-replace for ANSI chars in an array of 2-byte unicodes. Note that
' it's only used to remove known ANSI 'Latin' characters with a 'low' byte of
' zero: any other use of the two-byte 'step' will fail on non-Latin unicodes.
' ** THIS CODE IS IN THE PUBLIC DOMAIN **
' Nigel Heffernan Excellerando.Blogspot.com
Const FETCH_ROWS As Long = 4096
Dim COMMA As String * 1
Dim BLANK As String * 4
Dim EOROW As String * 2
COMMA = ChrW$(44)
BLANK = ChrW$(13) & ChrW$(10) & ChrW$(13) & ChrW$(10)
EOROW = ChrW$(13) & ChrW$(10)
Dim FetchArray As Variant
Dim i As Long ' i for rows in the output file, records in the recordset
Dim j As Long ' j for columns in the output file, fields in the recordset
Dim k As Long ' k for all other loops: bytes in individual data items
Dim i_Offset As Long
Dim i_LBound As Long
Dim i_UBound As Long
Dim j_LBound As Long
Dim j_UBound As Long
Dim k_lBound As Long
Dim k_uBound As Long
Dim hndFile As Long
Dim varField As Variant
Dim iRowCount As Long
Dim arrBytes() As Byte
Dim arrTemp1() As String
Dim arrTemp2() As String
Dim arrTemp3(0 To 2) As String
Dim boolNumeric As Boolean
Dim strHeader As String
Dim arrHeader() As Byte
Dim strFile As String
Dim strPath As String
Dim strExtn As String
strFile = FileName(OutputFile)
strPath = FilePath(OutputFile)
strExtn = FileExtension(strFile)
If rst Is Nothing Then Exit Function
If rst.State <> 1 Then Exit Function
If strExtn = "" Then
strExtn = ".csv"
End If
With FSO
If strFile = "" Then
strFile = .GetTempName
strFile = Left(strFile, Len(strFile) - Len(".tmp"))
strFile = strFile & strExtn
End If
If strPath = "" Then
strPath = TempSQLFolder
End If
If Right(strPath, 1) <> "\" Then
strPath = strPath & "\"
End If
strExtn = FileExtension(strFile)
If strExtn = "" Then
strExtn = ".csv"
strFile = strFile & strExtn
End If
OutputFile = strPath & strFile
End With
If FileName(OutputFile) <> "" Then
If Len(VBA.FileSystem.Dir(OutputFile, vbNormal)) <> 0 Then
Err.Clear
VBA.FileSystem.Kill OutputFile ' do it now, and reduce wait for deletion
If Err.Number = 70 Then ' permission denied: change the output file name
OutputFile = FileStripExtension(OutputFile) & "_" & FileStripExtension(FSO.GetTempName) & FileExtension(OutputFile)
End If
End If
End If
' ChrW$() gives a 2-byte 'Wide' char. This coerces all subsequent operations to UTF16
arrTemp3(0) = ChrW$(34) ' Encapsulating quote
arrTemp3(1) = vbNullString ' The field value will go here
arrTemp3(2) = ChrW$(34) ' Encapsulating quote
If rst.EOF And rst.BOF Then
FetchArray = Empty
ElseIf rst.EOF Then
rst.MoveFirst
End If
' An empty recordset must still write a header row of field names: we put this in the
' output buffer and write it to the file before we start looping through the records.
ReDim FetchArray(0 To rst.Fields.Count, 0 To 0)
i_LBound = 0
i_UBound = 0
If IsMissing(FieldList) Then
For j = LBound(FetchArray, 1) To UBound(FetchArray, 1) - 1 Step 1
FetchArray(j, i_UBound) = rst.Fields(j).Name
Next j
Else
j = 0
For Each varField In FieldList
j_UBound = j_UBound + 1
Next varField
ReDim arrTemp2(j_LBound To j_UBound)
For Each varField In FieldList
FetchArray(j, i_UBound) = CStr(varField)
j = j + 1
Next varField
End If
ReDim arrTemp1(i_LBound To i_UBound) ' arrTemp1 is the rowset we write to file
ReDim arrTemp2(j_LBound To j_UBound) ' arrTemp2 represents a single record
Do Until IsEmpty(FetchArray)
i_LBound = LBound(FetchArray, 2)
i_UBound = UBound(FetchArray, 2)
j_LBound = LBound(FetchArray, 1)
j_UBound = UBound(FetchArray, 1)
If UBound(arrTemp1) <> i_UBound + 1 Then
ReDim arrTemp1(i_LBound To i_UBound + 1)
arrTemp1(i_UBound + 1) = vbNullString ' The 'Join' operation will insert a trailing row
End If ' delimiter here (Not required by the last chunk)
If UBound(arrTemp2) <> j_UBound Then
ReDim arrTemp2(j_LBound To j_UBound)
End If
' Data body. This is heavily optimised to avoid VBA String functions with allocations
For i = i_LBound To i_UBound Step 1
' If this is confusing... Were you expecting FetchArray(i,j)? i for row, j for column?
' FetchArray comes from RecordSet.GetRows(), which returns a TRANSPOSED array: i and j
' are still the field and record ordinals, row(i) and column(j) in the output file.
For j = j_LBound To j_UBound
If IsNull(FetchArray(j, i)) Then
arrTemp2(j) = ""
Else
arrTemp2(j) = FetchArray(j, i) ' confused? see he note above
End If
If CleanupText Or (i_UBound = 0) Then ' (i_UBound=0): always clean up field names
arrBytes = arrTemp2(j) ' Integer arithmetic is faster than string-handling for
' this: all VBA string operations require an allocation
For k = LBound(arrBytes) To UBound(arrBytes) Step 2
Select Case arrBytes(k)
Case 10, 13, 9, 160
If arrBytes(k + 1) = 0 Then
arrBytes(k) = 32 ' replaces CR, LF, Tab, and non-breaking
End If ' spaces with the standard ANSI space
Case 44
If Not CoerceText Then
If arrBytes(k + 1) = 0 Then
arrBytes(k) = 32 ' replace comma with the ANSI space
End If
End If
Case 34
If arrBytes(k + 1) = 0 Then
arrBytes(k) = 39 ' replaces double-quote with single quote
End If
End Select
Next k
arrTemp2(j) = arrTemp2(j)
End If ' cleanup
If CoerceText Then ' encapsulate all fields in quotes, numeric or not
arrTemp3(1) = arrTemp2(j)
arrTemp2(j) = Join$(arrTemp3, vbNullString)
ElseIf (i = 0) And (i = i_UBound) Then ' always encapsulate field names
arrTemp3(1) = arrTemp2(j)
arrTemp2(j) = Join$(arrTemp3, vbNullString)
Else ' selective encapsulation, leaving numeric fields unencapsulated:
' we *could* do this by reading the ADODB field types: but that's
' slower, and you may be 'caught out' by provider-specific types.
arrBytes = arrTemp2(j)
boolNumeric = True
For k = LBound(arrBytes) To UBound(arrBytes) Step 2
If arrBytes(k) < 43 Or arrBytes(k) > 57 Then
If arrBytes(k) <> 69 Then
boolNumeric = False
Exit For
Else
If k > UBound(arrBytes) - 5 Then
boolNumeric = False
Exit For
ElseIf arrBytes(k + 2) = 45 Then
' detect "1.234E-05"
ElseIf arrBytes(k + 2) = 43 Then
' detect "1.234E+05"
Else
boolNumeric = False
Exit For
End If
End If
End If
Next k
If boolNumeric Then
For k = 1 + LBound(arrBytes) To UBound(arrBytes) Step 2
If arrBytes(k) <> 0 Then
boolNumeric = False
Exit For
End If
Next k
End If
arrBytes = vbNullString
If Not boolNumeric Then ' text field, encapsulate it
arrTemp3(1) = arrTemp2(j)
arrTemp2(j) = Join(arrTemp3, vbNullString)
End If
End If ' CoerceText
Next j
arrTemp1(i) = Join(arrTemp2, COMMA)
Next i
iRowCount = iRowCount + i - 2
' **** WHY WE 'PUT' A BYTE ARRAY INSTEAD OF A VBA STRING VARIABLE **** ****
'
' Put #hndFile, , StrConv(Join(arrTemp1, EOROW), vbUnicode)
' Put #hndFile, , Join(arrTemp1, EOROW)
'
' If you pass unicode, Wide or UTF-16 string variables to PUT, it prepends a
' Unicode Byte Order Mark to the data which, when written to your file, will
' render the field names illegible to Microsoft's JET ODBC and ACE-OLEDB SQL
' drivers (which can actually read unicode field names, if the helpful label
' isn't in the way). The primeval 'PUT' statement writes a Byte array as-is.
'
' **** **** **** **** **** **** **** **** **** **** **** **** **** **** ****
arrBytes = Join$(arrTemp1, vbCrLf)
If hndFile = 0 Then
i_Offset = 1
If Len(Dir(OutputFile)) > 0 Then
VBA.FileSystem.Kill OutputFile
End If
WaitForFileDeletion OutputFile
hndFile = FreeFile
Open OutputFile For Binary Access Write As #hndFile
End If
Put #hndFile, i_Offset, arrBytes
i_Offset = i_Offset + 1 + UBound(arrBytes)
Erase arrBytes
If rst.EOF Then
Erase FetchArray
FetchArray = Empty
Else
If IsMissing(FieldList) Then
FetchArray = rst.GetRows(FETCH_ROWS)
Else
FetchArray = rst.GetRows(FETCH_ROWS, , FieldList)
End If
End If
Loop ' until isempty(FetchArray)
If iRowCount < 1 Then '
iRowCount = 0 ' Row Count excludes the header
End If
RecordsetToCSV = iRowCount
ExitSub:
On Error Resume Next
If hndFile <> 0 Then
Close #hndFile
End If
Erase arrBytes
Erase arrTemp1
Erase arrTemp2
Exit Function
ErrSub:
Resume ExitSub
End Function
Public Function FilePath(Path As String) As String
' Strip the filename from a path, leaving only the path to the folder
' The last char of this path will be the backslash
' This does not check for the existence or accessibility of the file:
' all we're doing here is string-handling
Dim strPath As String
Dim arrPath() As String
Const BACKSLASH As String * 1 = "\"
strPath = Trim(Path)
If strPath = "" Then Exit Function
If Right$(strPath, 1) = BACKSLASH Then Exit Function
arrPath = Split(strPath, BACKSLASH)
If UBound(arrPath) = 0 Then ' does not contain "\"
FilePath = ""
Else
arrPath(UBound(arrPath)) = vbNullString
FilePath = Join$(arrPath, BACKSLASH)
End If
Erase arrPath
End Function
Public Function FileName(Path As String) As String
' Strip the folder and path from a file's path string, leaving only the file name
' This does not check for the existence or accessibility of the file:
' all we're doing here is string-handling
Dim strPath As String
Dim arrPath() As String
Const BACKSLASH As String * 1 = "\"
strPath = Trim(Path)
If strPath = "" Then Exit Function
If Right$(strPath, 1) = BACKSLASH Then Exit Function
arrPath = Split(strPath, BACKSLASH)
If UBound(arrPath) = 0 Then ' does not contain "\"
FileName = Path
Else
FileName = arrPath(UBound(arrPath))
End If
Erase arrPath
End Function
Public Function FileExtension(Path As String) As String
' Return the extension of the file
' This is just string-handling: no file or path validation is attempted
' The file extension is deemed to be whatever comes after the final '.'
' The extension is returned with the dot, eg: ".txt" not "txt"
' If no extension is detected, FileExtension returns an empty string
Dim strFile As String
Dim arrFile() As String
Const DOT_EXT As String * 1 = "."
strFile = FileName(Path)
strFile = Trim(strFile)
If strFile = "" Then Exit Function
If Right$(strFile, 1) = DOT_EXT Then Exit Function
arrFile = Split(strFile, DOT_EXT)
If UBound(arrFile) = 0 Then ' does not contain "\"
FileExtension = vbNullString
Else
FileExtension = arrFile(UBound(arrFile))
FileExtension = Trim(FileExtension)
If Len(FileExtension) > 0 Then
FileExtension = DOT_EXT & FileExtension
End If
End If
Erase arrFile
End Function
Public Function FileStripExtension(Path As String) As String
' Return the filename, with the extension removed
' This is just string-handling: no file validation is attempted
' The file extension is deemed to be whatever comes after the final '.'
' Both the dot and the extension are removed
Dim strFile As String
Dim arrFile() As String
Const DOT_EXT As String * 1 = "."
strFile = FileName(Path)
If strFile = "" Then Exit Function
If Right$(strFile, 1) = DOT_EXT Then Exit Function
strFile = Trim(strFile)
arrFile = Split(strFile, DOT_EXT)
If UBound(arrFile) = 0 Then ' does not contain "\"
FileStripExtension = vbNullString
Else
ReDim Preserve arrFile(LBound(arrFile) To UBound(arrFile) - 1)
FileStripExtension = Join$(arrFile, DOT_EXT)
End If
Erase arrFile
End Function
You'll also need the three path-and-file-name utility functions, if you don't have your own versions already:
FileName()
FilePath()
FileStripExtension()
There's room for improvement in the string-encapsulation logic: the correct approach is to look up the recordset's field types and apply quote marks accordingly, and it may well turn out to be faster than my clunky byte-counting.
However, my approach is all about the file consumers and what they expect to see; and that doesn't always line up with what they ought to accept.
If you succeed in coding a faster and more robust version do, please, let me know: if I'm asked to, I may well code up encapsulation by field type myself.
just thought I would toss in; macros offer this feature - and it is quite simple to set up;
select the export macro, select the query to export, select the format.... if you leave the destination selector blank it will launch the standard Windows file picker....
after a decade+ of coding in vba - macros have won me over for this particular function.....

extract numbers from string in access

I need help creating a VB code or expression in Access 2010 that will group numbers from a string where each set starts with number 6 and is always 9 characters long.
Example of strings:
Order Confirmation # 638917872-001 Partial Order/$23.74 RECEIVED
Order Confirmation - Multiple Orders - Order Confirmation#639069135-001/$297.45 - Order Confirmation#639069611-001/$32.08.
I'm using a VB code to remove all the alpha characters but that just leaves me with:
6389178720012374 from string 1 and
639069135001297456390696110013208 from string 2.
All I care about is the order number that starts with 6 and is 9 characters long. Any help would be greatly appreciated, I know there's an easier way.
VB.NET Solution:
If you just need the first 9 numbers from your resulting strings you could use String.Substring, ie:
Dim numberString as String = "6389178720012374"
Dim newString As String = numberString.Substring(0, 9)
MessageBox.Show(newString)
shows 638917872
MSDN Link
EDIT:
Maybe you would want to use a RegEx - something like this perhaps can get you started:
Private Sub Input()
Dim numberString As String = "Order Confirmation # 638917872-001 Partial Order/$23.74 RECEIVED"
Dim numberString2 As String = "Order Confirmation - Multiple Orders - Order Confirmation#639069135-001/$297.45 - Order Confirmation#639069611-001/$32.08"
GiveMeTheNumbers(numberString)
GiveMeTheNumbers(numberString2)
End Sub
Function GiveMeTheNumbers(ByVal s As String) As String
Dim m As Match = Regex.Match(s, "6\d{8}") 'get 9 digit #s begin w/6
Do While m.Success
MessageBox.Show(m.Value.ToString)
m = m.NextMatch()
Loop
Return False
End Function
Results -
MessageBox1: 638917872
MessageBox2: 639069135
MessageBox3: 639069611
You can use this function ... tested in VB.NET
Function NumOnly(ByVal s As String) As String
sRes = ""
For x As Integer = 0 To s.Length - 1
If IsNumeric(s.Substring(x, 1)) Then sRes = sRes & s.Substring(x, 1)
Next
return sRes
End Function
Little modif for ms-access
OK, here's a VBA solution. You'll need to add Microsoft VBScript Regular Expressions to your references.
This will match every 9 digit number it finds and return an array of strings with the order #s.
Function GetOrderNum(S As String) As String()
Dim oMatches As Object
Dim aMatches() As String
Dim I As Integer
Dim RE As Object
Set RE = CreateObject("vbscript.regexp")
ReDim aMatches(0)
RE.Pattern = "\d{9}"
RE.Global = True
RE.IgnoreCase = True
Set oMatches = RE.Execute(S)
If oMatches.Count <> 0 Then
ReDim aMatches(oMatches.Count)
For I = 0 To oMatches.Count - 1
aMatches(I) = oMatches(I)
Next I
End If
GetOrderNum = aMatches
End Function

Is it possible to write this VBA code any better?

Am I reinventing the wheel here? Is there a better way to do this? This VBA function looks for the first instance of a string in the comment field of a form in Access containing 20 characters or less, no spaces, surrounded by (~) tildes, then returns it.
Public Function ParseComment(strComment As String) As String
' This function parses the comment field of the job entry dialog for (~) tilde
' surrounded text, then returns that text.
Dim intCounter As Integer
Dim intFirstChar As Integer
Dim intLastChar As Integer
Dim strResult As String
intFirstChar = 0
intLastChar = 0
intCounter = 0
Do While (intLastChar = 0) And (intCounter < Len(strComment))
intCounter = intCounter + 1
strCharacter = Mid(strComment, intCounter, 1)
If (strCharacter = "~") Then
If intFirstChar Then
intLastChar = intCounter
Else
intFirstChar = intCounter + 1
End If
End If
Loop
strResult = Mid(strComment, intFirstChar, intLastChar - intFirstChar)
If (intLastChar - intFirstChar <= 20) And (intFirstChar <> 0 Or intLastChar <> 0) And Not InStr(strResult, " ") Then
ParseComment = strResult
End If
End Function
Thanks much.
I would use InStr to find the first and second occurences of the ~ character, something like this, rather than looping manually:
Public Function ParseComment(strComment As String) As String
' This function parses the comment field of the job entry dialog for (~) tilde
' surrounded text, then returns that text.
Dim firstTilde As Integer
Dim secondTilde As Integer
Dim strResult As String
firstTilde = 0
secondTilde = 0
strResult = ""
firstTilde = InStr(strComment, "~")
If firstTilde > 0 Then
secondTilde = InStr(firstTilde + 1, strComment, "~")
If (secondTilde > 0) And (secondTilde < 20) Then
strResult = Mid(strComment, firstTilde, secondTilde)
If InStr(strResult, " ") = 0 Then
ParseComment = strResult
End If
End If
End If
End Function
[Disclaimer, I haven't tested this!]
Using the built-in functions might be a little quicker, but don't imagine it will make a critical difference...
Something like:
Public Function getTildeDelimStringPart(inputstring As String) As String
Dim commentStart As Long, commentEnd As Long
commentStart = InStr(1, inputstring, "~")
If commentStart = 0 Then ' no tilde
getTildeDelimStringPart = vbNullString
Exit Function
End If
commentEnd = InStr(1 + commentStart, inputstring, "~")
If commentEnd = 0 Then
getTildeDelimStringPart = vbNullString
Exit Function
End If
getTildeDelimStringPart = Mid(inputstring, commentStart, commentEnd - commentStart + 1)
End Function
I see everyone has given you some more ways to do this (instr is a great way, see Vicky's answer!), so I'll just list up some tips on optimizing your code:
Use Long instead of Integer. VBA will convert them to Long every time.
Default value for Int and Long is 0 in VBA, so no need to declare them so.
Use Mid$ instead of Mid
Using Instr() would be a very effecient way to find location of ~
Fun Tip: If you do want to evaluate each character, fastest way is numeric comparision:
if Asc(Mid$(strComment, intCounter, 1)) = 126 Then
This worked for me:
Public Function ParseComment(strComment As String) As String
Dim regex As Object ' VBScript_RegExp_55.RegExp
Dim regexmatch As Object ' VBScript_RegExp_55.MatchCollection
Set regex = CreateObject("VBScript_RegExp_55.RegExp")
With regex
.MultiLine = False
.Global = False
.IgnoreCase = True
.Pattern = "(~[^ ~]{1,20}~)"
End With
Set regexmatch = regex.Execute(strComment)
If regexmatch.Count > 0 Then
ParseComment = regexmatch(0)
End If
End Function
You can add additional parsing at the end if you want to remove the tilde characters.
I tested it on the following string:
ABC~123aA%dwdD~CBA
the function returns ~123aA%dwdD~
Forgot to mention that this code requires VBScript Regular Expressions 5.5 which is located in %windir%\system32\vbscript.dll\3, although the code is late bound so you should just be able to drop it into your project.