Doing Email Validations for Microsoft Access? - ms-access

I am currently working on an Access project, and I am having issues with validating the Email Field for my project. I want the emails to have a mandatory string, a mandatory # symbol, and mandatory letters and numbers after the # symbols.
Currently, my validation looks like this:
Like "* # *"
This works perfectly with what I want; however, it still accepts entries that do not have letters, numbers, a period and dash beforehand. Any tips and suggestions on how to go about this and any resources where I can learn validation?

Another option to use Regular expressions. You can easily create your own pattern rather than using tones of ifs.
something like this:
Public Function FN_REGEXP_IS_EMAIL(email As String) As Boolean
If IsBlank(email) Then Exit Function
Const emailPattern As String = "^([\w\-\.]+)#((\[([0-9]{1,3}\.){3}[0-9]{1,3}\])|(([\w\-]+\.)+)([a-zA-Z]{2,4}))$"
On Error Resume Next
With CreateObject("vbscript.RegExp")
.Pattern = emailPattern
FN_REGEXP_IS_EMAIL = .test(email)
End With
End Function

If the validation occurs during manual entry of the email - then you can use a Mask. These are a text field property you'll want to read about online.
If the validation is needed on an existing set of data, not during the input phase, - or if you find that a Mask just is not suitable for some reason - then you'll need custom queries & code to check - and there is no single answer, it has to be crafted to meet all your requirements..

We use this function - which is readable:
Public Function IsEmailAddress( _
ByVal strEmailAddresses As String) _
As Boolean
' Checks if strEMailAddr could represent one or more valid e-mail addresses.
' Does not check validity of domain names.
'
' 2003-06-22. Cactus Data ApS, CPH
' 2018-12-01. Expanded to allow for and validate multiple addresses.
' Allowed characters.
Const cstrValidChars As String = "#_-.0123456789abcdefghijklmnopqrstuvwxyz"
Const cstrDot As String = "."
Const cstrAt As String = "#"
' Minimum length of an e-mail address (a#a.ca).
Const cintAddressLenMin As Integer = 6
' Address separator.
Const cstrSeparator As String = ";"
Dim avarAddresses As Variant
Dim Index As Integer
Dim strEmailAddr As String
Dim strValidChars As String
Dim booFailed As Boolean
Dim intPos As Integer
Dim intI As Integer
avarAddresses = Split(strEmailAddresses, cstrSeparator)
For Index = LBound(avarAddresses) To UBound(avarAddresses)
strEmailAddr = avarAddresses(Index)
' Strip a display name.
CleanEmailAddress strEmailAddr
' Convert to lowercase.
strEmailAddr = LCase(strEmailAddr)
' Check that strEMailAddr contains allowed characters only.
For intI = 1 To Len(strEmailAddr)
If InStr(cstrValidChars, Mid(strEmailAddr, intI, 1)) = 0 Then
booFailed = True
End If
Next
If booFailed = False Then
' Check that the first character is not cstrAt.
booFailed = Left(strEmailAddr, 1) = cstrAt
If booFailed = False Then
' Check that the first character is not a cstrDot.
booFailed = Left(strEmailAddr, 1) = cstrDot
If booFailed = False Then
' Check that length of strEMailAddr exceeds
' minimum length of an e-mail address.
intPos = Len(strEmailAddr)
booFailed = (intPos < cintAddressLenMin)
If booFailed = False Then
' Check that none of the last two characters of strEMailAddr is a dot.
booFailed = (InStr(intPos - 1, strEmailAddr, cstrDot) > 0)
If booFailed = False Then
' Check that strEMailAddr does contain a cstrAt.
intPos = InStr(strEmailAddr, cstrAt)
booFailed = (intPos = 0)
If booFailed = False Then
' Check that strEMailAddr does contain one cstrAt only.
booFailed = (InStr(intPos + 1, strEmailAddr, cstrAt) > 0)
If booFailed = False Then
' Check that the character leading cstrAt is not cstrDot.
booFailed = (Mid(strEmailAddr, intPos - 1, 1) = cstrDot)
If booFailed = False Then
' Check that the character following cstrAt is not cstrDot.
booFailed = (Mid(strEmailAddr, intPos + 1, 1) = cstrDot)
If booFailed = False Then
' Check that strEMailAddr contains at least one cstrDot
' following the sign after cstrAt.
booFailed = Not (InStr(intPos, strEmailAddr, cstrDot) > 1)
End If
End If
End If
End If
End If
End If
End If
End If
End If
If booFailed = True Then
Exit For
End If
Next
IsEmailAddress = Not booFailed
End Function
Also, it allows for multiple addresses, like:
"joe#example.com;ann#domain.org"
Further, if addresses are copy-pasted, these may be extended with a leading display name. That is allowed as well, as the addresses are "cleaned" before validating using this function:
' Strips a full e-mail address with display name like:
'
' "John Doe <john.doe#example.com>"
'
' to the e-mail address only:
'
' "john.doe#example.com"
'
' 2018-12-05. Gustav Brock, Cactus Data ApS, CPH.
'
Public Sub CleanEmailAddress(ByRef EmailAddress As String)
If Trim(EmailAddress) = "" Then
EmailAddress = ""
Else
EmailAddress = Split(StrReverse(Split(StrReverse(EmailAddress), "<")(0)), ">")(0)
End If
End Sub

Try:
like "*[#]*[.]*"
The above means "some chars - any kind", then MUST have a #,
then "some chars - any kind", then MUST have a . (dot)
Then some chars - any kind.
Edit
To force having characters in each part, use this:
like "?*[#]?*[.]?*"
So above is:
must have at least one char (?),
then any number of chars (*),
then MUST have a # sign ([#]),
then must have at least one char (?),
then any number of chars (*),
then must have a dot ([.]),
then must have one char (?),
then any number of chars (*)

Related

Hyphenation in SSRS

I want to add hyphenation to the column headers of a tablix
Consider the column value "waterbodiesinhereforme"
Currently SSRS is hyphenating based on the size it can fit inside the tablix column header. Like below .
waterbodiesinhereforme
But my requirement is
waterbodiesin-
hereforme
So far I have tried the soft hyphen character , ­ which did not work in the ssrs even though html rendering was set to true. Even the Unicode "00AD" did not work.
When I tried with the ZeroWidthCharacter it worked correctly, but I do not know how to introduce a hyphen when there is a new line.
Zero Width Character Example
="water" + ChrW(&h200B) + "bodies" + ChrW(&h200B) + "in" + ChrW(&h200B) + "here" + ChrW(&h200B) + "for" + ChrW(&h200B) + "me"
Things I cannot do
- Hardcode the hyphen (not acceptable because this value is dynamic)
I've written this in Excel VBA, but this can be easily transferred to SSRS.
This splits the input string into parcels of 10 characters separated by carriage returns. You can change the string length by changing the initial value of IntSplit. You could add your zerowidthcharacter if you wanted. The Function code would need to be added to the "Report Properties>Code" section of the SSRS, with the string requiring the split being placed in the Expression for the field:
=code.SplitString(Fields!YourFieldName.value)
Here's the code ...
Private Sub do_it()
Dim strString As String
Dim StrNewString As String
strString = "This is a very long sentence that needs to be chunked up"
strString = SplitString(strString)
Debug.Print strString
End Sub
Private Function SplitString(ByVal strInput As String) As String
Dim StrOut As String
Dim IntSplit As Integer
Dim Intstart As Integer
Dim j As Integer
IntSplit = 10
Intstart = 1
StrOut = ""
For j = 1 To Len(strInput)
If Int(j / IntSplit) = j / IntSplit Then
StrOut = StrOut + Mid(strInput, Intstart, IntSplit) + vbCrLf
Intstart = j + 1
End If
Next
StrOut = StrOut + Mid(strInput, Intstart, Len(strInput) - (Intstart - 1))
SplitString = StrOut
'Return SplitString ' A Return statement is required in SSRS
End Function
Output
This is a
very long
sentence t
hat needs
to be chun
ked up

Leading and Trailing Spaces

I've come up with a bit of a weird situation. I have a string that was entered into a form from a webpage. I noticed that the string wasn't behaving as expected when I was trying to apply a filter.
The crux of the matter is depending on how I view the string it appears differently.
Form View - "523548"
Datasheet View - " 523548"
Raw Sql - " 523548"
Actually, when I view the datasheet value it appears as "523548 " but copies as " 523548".
Asc(Left(string),1) tells me the first character is Chr9 (Tab Key)
I am really stuck to find out why this is happening or more importantly, what I can do to correct it.
Thanks!
Dave.
I'd use the Trim() function here. Although the link is for Excel, it's the same syntax in Access.
Use this function when you write the value to the table, and you can use this function to clean up your current data as well. This should remove phantom tabs, as well as spaces.
Thanks for the tips. I found this function and added in a couple of items to suit my needs.
' Strip Illegal Characters
' http://www.utteraccess.com/wiki/index.php/Strip_Illegal_Characters
' Code courtesy of UtterAccess Wiki
' Licensed under Creative Commons License
' http://creativecommons.org/licenses/by-sa/3.0/
'
' You are free to use this code in any application,
' provided this notice is left unchanged.
'
' rev date brief descripton
' 1.0 2010-10-23 Writing files to disk that contain illegal file characters can cause sometimes obscure error message(s)
'
Public Function fStripIllegal(strCheck As String, Optional strReplaceWith As String = "") As String
On Error GoTo StripIllErr
'illegal file name characters included in default string are ? [ ] / \ = + < > :; * " , '
Dim intI As Integer
Dim intPassedString As Integer
Dim intCheckString As Integer
Dim strChar As String
Dim strIllegalChars As String
Dim intReplaceLen As Integer
If IsNull(strCheck) Then Exit Function
strIllegalChars = "?[]/\=+<>:;,*" & Chr(34) & Chr(39) & Chr(32) & Chr(9) 'add/remove characters you need removed to this string
intPassedString = Len(strCheck)
intCheckString = Len(strIllegalChars)
intReplaceLen = Len(strReplaceWith)
If intReplaceLen > 0 Then 'a character has been entered to use as the replacement character
If intReplaceLen = 1 Then 'check the character itself isn't an illegal character
If InStr(strIllegalChars, strReplaceWith) > 0 Then
MsgBox "You can't replace an illegal character with another illegal character", _
vbOKOnly + vbExclamation, "Invalid Character"
fStripIllegal = strCheck
Exit Function
End If
Else 'only one replacement character allowed
MsgBox "Only one character is allowed as a replacement character", _
vbOKOnly + vbExclamation, "Invalid Replacement String"
fStripIllegal = strCheck
Exit Function
End If
End If
If intPassedString < intCheckString Then
For intI = 1 To intCheckString
strChar = Mid(strIllegalChars, intI, 1)
If InStr(strCheck, strChar) > 0 Then
strCheck = Replace(strCheck, strChar, strReplaceWith)
End If
Next intI
Else
For intI = 1 To intPassedString
strChar = Mid(strIllegalChars, intI, 1)
If InStr(strCheck, strChar) > 0 Then
strCheck = Replace(strCheck, strChar, strReplaceWith)
End If
Next intI
End If
fStripIllegal = Trim(strCheck)
StripIllErrExit:
Exit Function
StripIllErr:
MsgBox "The following error occured: " & err.Number & vbCrLf _
& err.Description, vbOKOnly + vbExclamation, "Unexpected Error"
fStripIllegal = strCheck
Resume StripIllErrExit
End Function
Well, adjust your routine to not include the tab for new entries.
The old entries you can adjust with Replace:
NewValue = Replace([OldValue], Chr(9), "")

How can I split a list of strings to display each word individually?

At the moment I have a function which returns a list of strings. It takes a word from a view and then I would like to return to the view after the function displaying the individual words in different text boxes. However, I will not know how many words will be returned as I don't know how many the user will enter. It's only for improving my skills so it will be less than five. I have looked this up and haven't really found any results which help. I thought about splitting it up on the view itself which could work or even in my ViewModel and then returning that to the view. However, like I said. I can't simply put down three text boxes as I don't know for sure how many words will be entered. Can anybody help? I have posted the two areas below where I think I can split the list. Thank you.
View:
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
<h2>Split Result</h2>
<!-- Wont work. Fix tomorrow. Split array-->
<label>Split Words</label>
<%: Html.TextBox("txtEncodeResult", Model.SplitWords)%>
</asp:Content>
Model:
Function Split(inSentence) As List(Of String)
'
' Get the posted information from the form
Dim sSentence As String = inSentence
'
' Create a list of string to put the sentene in and create a new instance of it
Dim sSplitWords As List(Of String) = New List(Of String)
'
' Find the position of the first space in sSentence
Dim iPos As Integer
iPos = InStr(sSentence, " ")
'Find the length of the sentence
Dim iLen As Integer
iLen = Len(sSentence)
'
' Create the remaining length
Dim iRemainingLength As Integer = 0
'
' Create sProp as string and set sProp to equal sSentence
Dim sProp As String = ""
sProp = sSentence
'
'Do while the position is not equal to 0
Do While iPos <> 0
'
' Find the left most characters from the position - 1 in sSentence and then set this string as sProp
sProp = Left(sSentence, iPos - 1)
'
' Add the first word to the List
sSplitWords.Add(sProp)
'
' Find the new remaining length
iRemainingLength = iLen - iPos
'
' Get the rest of the sentence minus the word which has already been taken away.
sSentence = sSentence.Substring(iPos, iRemainingLength)
'
' Find the new position of the space in sSentence
iPos = InStr(sSentence, " ")
'
' Find the length of sSentence
iLen = Len(sSentence)
'
'Loop while the condition is true
Loop
If iPos = 0 Then
sSplitWords.Add(sSentence)
End If
'
' Return the array
Return sSplitWords
End Function
Like I said, I am simply improving my programming skills so it is very basic. Thanks.
You can dramatically reduce the code in your Split function, to one line in fact
Function Split(inSentence) As List(Of String)
Return (inSentence ?? "").Split(" ").ToList();
End Function
Then in your view (assuming you are passing the list as the model) you can generate a new text box per word
#For Dim i as Integer = 0 To Model.Count-1
#: Html.TextBox("tb" & i.ToString(), Model[i])
Next

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.....

Search and replace whole words which can be separated not only by a space

I'm looking for a way to search and replace whole words. The whole words can be separated not only by a space but .,;:/? etc.
I'm looking to do something like this
replace([address], ***--list of separators, like .,;:/?--*** & [replacewhat] & ***--list of separators, like .,;:/?--*** ," " & [replacewith] & " ")
I don't know how to pass a list of separators instead of running a replace function once for each combination of separators (which combined with 300 words I'm replacing would amount to an insane number of queries).
You can do a replacement with a regular expression using a pattern with the \b marker (for the word boundary) before and after the word you want to replace.
Public Function RegExpReplaceWord(ByVal strSource As String, _
ByVal strFind As String, _
ByVal strReplace As String) As String
' Purpose : replace [strFind] with [strReplace] in [strSource]
' Comment : [strFind] can be plain text or a regexp pattern;
' all occurences of [strFind] are replaced
' early binding requires reference to Microsoft VBScript
' Regular Expressions:
'Dim re As RegExp
'Set re = New RegExp
' with late binding, no reference needed:
Dim re As Object
Set re = CreateObject("VBScript.RegExp")
re.Global = True
're.IgnoreCase = True ' <-- case insensitve
re.pattern = "\b" & strFind & "\b"
RegExpReplaceWord = re.Replace(strSource, strReplace)
Set re = Nothing
End Function
As written, the search is case sensitive. If you want case insensitive, enable this line:
re.IgnoreCase = True
In the Immediate window ...
? RegExpReplaceWord("one too three", "too", "two")
one two three
? RegExpReplaceWord("one tool three", "too", "two")
one tool three
? RegExpReplaceWord("one too() three", "too", "two")
one two() three
? RegExpReplaceWord("one too three", "to", "two")
one too three
? RegExpReplaceWord("one too three", "t..", "two")
one two three
... and for your range of delimiters ...
? RegExpReplaceWord("one.too.three", "too", "two")
one.two.three
? RegExpReplaceWord("one,too,three", "too", "two")
one,two,three
? RegExpReplaceWord("one;too;three", "too", "two")
one;two;three
? RegExpReplaceWord("one:too:three", "too", "two")
one:two:three
? RegExpReplaceWord("one/too/three", "too", "two")
one/two/three
? RegExpReplaceWord("one?too?three", "too", "two")
one?two?three
? RegExpReplaceWord("one--too--three", "too", "two")
one--two--three
? RegExpReplaceWord("one***too***three", "too", "two")
one***two***three
Thank you for your answer. It was of great help to me.
However, as the number of iterations of this code increased due to increase in my data size, I realized that this piece of code is slowing down my application. For instance, 10,000 iterations of this code take about 20 seconds.
I was using below code based on your answer:
Function CleanString(ByVal InputString As String, Optional SplWords = "USP|BP|EP|IP|JP", _
Optional Delim As String = "|") As String
Dim i As Integer
Dim ArrIsEmpty As Boolean
Dim ArrSplWords() As String
Dim Wrd As Variant
Dim RE As Object
CleanString = InputString
ArrSplWords = Split(SplWords, Delim)
Set RE = CreateObject("VBScript.RegExp")
RE.Global = True
RE.ignorecase = True
For Each Wrd In ArrSplWords
RE.Pattern = "\b" & Wrd & "\b"
If RE.test(CleanString) Then
CleanString = RE.Replace(CleanString, "")
End If
Next Wrd
CleanString = Application.WorksheetFunction.Trim(CleanString)
End Function
To tackle the issue of slowness, I decided to ditch the RegExp approach and came up with below code. Based on my evaluation, the below function is about 25 times faster (I timed it using timer function over 1000 iterations of each code).
Function CleanString(ByVal InputString As String, Optional SplWords As String = "USP|BP|EP|IP|JP", _
Optional Delim As String = "|", Optional WordSeparator As String = " ", _
Optional SplChar As String = "~|`|!|#|#|$|%|^|&|*|-|+|=|'|<|>|,|.|/|\|?|:|;") As String
Dim TestStr As String
Dim ArrSplChar() As String
Dim Char As Variant
Dim TestWords() As String
Dim Wrd As Variant
Dim Counter As Integer
TestStr = InputString
ArrSplChar = Split(SplChar, Delim, -1, vbTextCompare)
For Each Char In ArrSplChar
TestStr = Replace(TestStr, Char, WordSeparator & Char & WordSeparator, 1, -1, vbTextCompare)
Next Char
TestWords = Split(TestStr, WordSeparator, -1, vbTextCompare)
For Each Wrd In TestWords
Counter = IIf(Wrd = "", Counter + 1, Counter)
If InStr(1, LCase(SplWords), LCase(Wrd), vbTextCompare) = 0 Then
CleanString = CleanString & " " & Wrd
Counter = Counter + 1
End If
Next Wrd
CleanString = IIf(Counter - 1 = UBound(TestWords) - LBound(TestWords), _
Application.WorksheetFunction.Trim(InputString), _
Application.WorksheetFunction.Trim(CleanString))
End Function
This function looks a little messier than the regExp based function, but it is faster than the regExp based function.
Both the above functions generate the same output and can be called as follows:
Sub TestSub()
Debug.Print CleanString("Paracetamol USP")
End Sub
This will print "Paracetamol" in the immediate window.