I'm trying to change or update the code from detecting commas for pages,
The code below shows how to input pages with commas,
sample: 1,2,5,3,8 and it would not accept 0, or greater than the maximum page
What I'm asking is to add the code that would accept
like this:
2-5,8,9
or
8,9,2-5
or
2-5,8-10
so it means, pages to print are 2,3,4,5,8,9,10
BUT it will NOT accept input like 2-5,4,8,9 because 4 was already used in 2-5
if that could be hard then its ok to input a simple range like:
2-5 and no commas, so user could not input commas, if they want to input
comma then the -sign could not be input also.
''CODED By: Chris, Combined to MackieChan solution
Public Function isCELLPageNumb(ByRef valyo As String, ByVal origMaxPage As Integer) As Boolean
Dim rgxNumberWithComma As New System.Text.RegularExpressions.Regex("^([0-9]+,?)+$")
Dim match = rgxNumberWithComma.Match(valyo)
If Not match.Success Then
Return False
Else
Dim numbers As New List(Of Integer) 'will store added numbers
For Each Item In valyo.Split(","c)
Dim intValue As Integer
'Check if number is a valid integer
'Check if number is 0
'Check if number has already added the number list
'Check if number is greater that MaxPage
If Not Integer.TryParse(Item, intValue) _
OrElse intValue > origMaxPage _
OrElse intValue = 0 _
OrElse numbers.Contains(intValue) Then
Return False
Else
'Item is valid, continue
numbers.Add(intValue)
End If
Next
End If
Return True
End Function
Private Sub DbGridPapers_CellEndEdit(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DbGridPapers.CellEndEdit
Dim pagestoprint As String = Nothing
Try
pagestoprint = DbGridPapers.Rows(e.RowIndex).Cells(1).Value.ToString
Catch ex As Exception
End Try
If (e.ColumnIndex = 1) And (pagestoprint IsNot Nothing) Then
If Not isCELLPageNumb(DbGridPapers.Rows(e.RowIndex).Cells(1).Value, OrigPage(e.RowIndex)) Then
MyThreadedControl(lbltest, "Text", "INVALID INPUT FOR [PAGES] AT ROW " & (e.RowIndex).ToString)
DbGridPapers.Rows(e.RowIndex).Cells(1).Value = OrigPage(e.RowIndex)
Return
Else
MyThreadedControl(lbltest, "Text", "The Maximum Page is:" & OrigPage(e.RowIndex).ToString)
End If
Dim pageDest As String = Nothing
If Me.btnpaperpay.Enabled Then
pageDest = DbGridPapers.Rows(e.RowIndex).Tag & "\"
Else
pageDest = docPrintnationPath & "\"
End If
Dim filename As String = pageDest & DbGridPapers.Rows(e.RowIndex).HeaderCell.Value.ToString
Dim OldRegularPrice As Decimal = DbGridPapers.Rows(e.RowIndex).Cells(3).Value
Dim FILEpages As New List(Of Integer)
''IF , AND - CAN BE MIX TO GET THE PAGE THEN ITS BETTER, AND I HAVE TO UPDATE THE CODE HERE ALSO.
If pagestoprint.Split(",").Length > 1 Then 'Split Length is +1 based
Dim pageFILES() As String = pagestoprint.Split(",")
For Each filePids As Integer In pageFILES
FILEpages.Add(filePids) ''GET range in comma sample page1,page3,page8,page2
Next
ElseIf pagestoprint.Split("-").Length > 1 Then 'Split Length is +1 based
Dim pageFILES() As String = pagestoprint.Split("-")
For page As Integer = pageFILES(0) To pageFILES(1)
FILEpages.Add(page) ''GET range sample pages2 to page5
Next
Else
Dim pages As Integer
If (Integer.TryParse(pagestoprint, pages)) Then
If pages = OrigPage(e.RowIndex) Then
DbGridPapers.Rows(e.RowIndex).Cells(2).Value = OrigImage(e.RowIndex)
DbGridPapers.Rows(e.RowIndex).Cells(3).Value = OrigPay(e.RowIndex)
DbGridPapers.Rows(e.RowIndex).Cells(4).Value = OrigCountedImage(e.RowIndex)
GoTo pCounter ''Return Original Cells Value
Else
FILEpages.Add(pages)''GET single page only
End If
End If
End If
pCounter:
Dim paperToTpay As Decimal = txtpapertotpay.Text.Substring(0, txtpapertotpay.Text.LastIndexOf(" "))
paperToTpay -= OldRegularPrice
paperToTpay += DbGridPapers.Rows(e.RowIndex).Cells(3).Value
MyThreadedControl(txtpapertotpay, "Text", paperToTpay.ToString & " dollar(s)")
End If
End Sub
I think its very hard.
I understand that a custom algorithm is acceptable. Here you have an approach accounting for all the described conditions:
Private Function extractPages(ByVal inputString As String) As List(Of Integer)
Dim outList As List(Of Integer) = New List(Of Integer)
If (inputString.Contains(",")) Then
outList = extractCommas(inputString, outList)
ElseIf (inputString.Contains("-")) Then
outList = extractDashes(inputString, outList)
End If
If (outList.Count > 0) Then
For i As Integer = outList.Count - 1 To 0 Step -1
If (outList.IndexOf(outList(i)) <> outList.LastIndexOf(outList(i))) Then
'Repeated item
'It can be just deleted or shall the function return an error?
outList.RemoveAt(i)
End If
Next
End If
Return outList
End Function
Private Function extractCommas(ByVal inputString As String, curList As List(Of Integer)) As List(Of Integer)
If (inputString.Contains(",")) Then
Dim temp() As String = inputString.Split(",")
For Each item In temp
If (Not item.Contains("-") And IsNumeric(item)) Then
If (Convert.ToInt32(item.Trim()) > 0) Then
curList.Add(Convert.ToInt32(item.Trim()))
End If
ElseIf (item.Contains("-")) Then
curList = extractDashes(item.Trim(), curList)
End If
Next
End If
Return curList
End Function
Private Function extractDashes(ByVal inputString As String, curList As List(Of Integer)) As List(Of Integer)
If (inputString.Contains("-")) Then
Dim temp() = inputString.Split("-")
If (temp.Length = 2) Then
If (Convert.ToInt32(temp(0)) <= Convert.ToInt32(temp(1))) Then
Dim count As Integer = Convert.ToInt32(temp(0)) - 1
If (count < 0) Then
count = 0
End If
Do
count = count + 1
curList.Add(count)
Loop While (count < Convert.ToInt32(temp(1)))
End If
End If
End If
Return curList
End Function
You can call extractPages and get all the page numbers:
Dim InputString As String = "2-5, 4,8-10"
Dim allPages As List(Of Integer) = extractPages(InputString) 'It returns 2, 3, 4, 5, 8, 9, 10
Description
It's not exactly clear what you're looking for, but this powershell solution shows the logic behind how I would approach the problem to allow a user to input 0,2-5,4,8,9 so that the zero and extra redundant digits are ignored.
Example
$string = "0,2-5,4,8,9"
[hashtable]$hashPages = #{}
foreach ($chunk in $String -split ",") {
# if the string has a dash then process it as a range
if ($chunk -match "(\d+)-(\d+)") {
# itterate through all the pages in the range
foreach ($Page in $Matches[1] .. $Matches[2]) {
# insert this page into a hash, which will keep the numbers unique
$hashPages[[string]$Page] = $true
} # next page
} # end if
# if string is only a number then process it as a single number
if ($chunk -match "(\d+)") {
# insert this page into a hash, which will keep the numbers unique
$hashPages[[string]$Matches[1]] = $true
} # end if
} # next chunk
# remove the undesireable numbers like zero if they were added
$hashPages.Remove("0");
Write-Host "these pages where requested:" $(($hashPages.Keys | sort ) -join ",")
Output
these pages where requested: 2,3,4,5,8,9
Related
Right now, the input is like a list of fields:
"field1", "field2", field3", ...
And each of those fields match up with a string in the fields array based on the client's specifications.
I parse all of the fields and put them into a dictionary to use outside the function, the key being their corresponding name from the fields array. Then as a check if the input isn't way off base, I compare the length of the dictionary to the length of the fields array (+- 1 for a bit of flexibility).
Finally, the function returns True or False based on how "off" the input field length is from the fields array. The problem is the client will add additional fields like that may cause errors.
Is there a more flexible way to do this check to reduce the chance of these errors in the future?
Greatly appreciate your input.
Public Function parseCsv(ByVal csv As String, ByRef dict As Dictionary(Of String, String)) As Boolean
Dim fields As String() = New String() {"Patient_Name", "Date_of_Birth", "Address", "County"}
dict = New Dictionary(Of String, String)
Dim csvRegex As String = "(?:,""|^"")(""""|[\w\W]*?)(?="",|""$)|(?:,(?!"")|^(?!""))([^,]*?)(?=$|,)|(\r\n|\n)"
Dim fieldCount As Integer = 0
Try
Dim matches As MatchCollection = Regex.Matches(csv, csvRegex)
Dim m As Match
For index As Integer = 0 To matches.Count - 1
m = matches(index)
Dim fieldValue As String = ""
If m.Groups(1).ToString() IsNot "" Then
fieldValue = m.Groups(1).ToString()
ElseIf m.Groups(2).ToString() IsNot "" Then
fieldValue = m.Groups(2).ToString()
ElseIf m.Groups(3).ToString() IsNot "" Then
fieldValue = m.Groups(3).ToString()
End If
If index < fields.Length Then
dict.Add(fields(index), fieldValue)
End If
fieldCount += 1
Next
Catch ex As Exception
Return False
End Try
Return fieldCount = fields.Length Or fieldCount = fields.Length - 1 Or fieldCount = fields.Length + 1
End Function
Unfortunately, the input comes in as a big list of data and I can't control what it looks like.
I have a variable text field sitting in cell A1 which contains the following:
Text;#Number;#Text;#Number
This format can keep repeating, but the pattern is always Text;#Number.
The numbers can vary from 1 digit to n digits (limit 7)
Example:
Original Value
MyName;#123;#YourName;#3456;#HisName;#78
Required value:
123, 3456, 78
The field is too variable for excel formulas from my understanding.
I tried using regexp but I am a beginner when it comes to coding. if you can break down the code with some explanation text, it would be much appreciated.
I have tried some of the suggestions below and they work perfectly. One more question.
Now that I can split the numbers from the text, is there any way to utilize the code below and add another layer, where we split the numbers into x cells.
For example: once we run the function, if we get 1234, 567 in the same cell, the function would put 1234 in cell B2, and 567 in cell C2. This would keep updating all cells in the same row until the string has exhausted all of the numbers that are retrieved from the function.
Thanks
This is the John Coleman's suggested method:
Public Function GetTheNumbers(st As String) As String
ary = Split(st, ";#")
GetTheNumbers = ""
For Each a In ary
If IsNumeric(a) Then
If GetTheNumbers = "" Then
GetTheNumbers = a
Else
GetTheNumbers = GetTheNumbers & ", " & a
End If
End If
Next a
End Function
If the pattern is fixed, and the location of the numbers never changes, you can assume the numbers will be located in the even places in the string. This means that in the array result of a split on the source string, you can use the odd indexes of the resulting array. For example in this string "Text;#Number;#Text;#Number" array indexes 1, 3 would be the numbers ("Text(0);#Number(1);#Text(2);#Number(3)"). I think this method is easier and safer to use if the pattern is indeed fixed, as it avoids the need to verify data types.
Public Function GetNums(src As String) As String
Dim arr
Dim i As Integer
Dim result As String
arr = Split(src, ";#") ' Split the string to an array.
result = ""
For i = 1 To UBound(arr) Step 2 ' Loop through the array, starting with the second item, and skipping one item (using Step 2).
result = result & arr(i) & ", "
Next
If Len(result) > 2 Then
GetNums = Left(result, Len(result) - 2) ' Remove the extra ", " at the end of the the result string.
Else
GetNums = ""
End If
End Function
The numbers can vary from 1 digit to n digits (limit 7)
None of the other responses seems to take the provided parameters into consideration so I kludged together a true regex solution.
Option Explicit
Option Base 0 '<~~this is the default but I've included it because it has to be 0
Function numsOnly(str As String, _
Optional delim As String = ", ")
Dim n As Long, nums() As Variant
Static rgx As Object, cmat As Object
'with rgx as static, it only has to be created once; beneficial when filling a long column with this UDF
If rgx Is Nothing Then
Set rgx = CreateObject("VBScript.RegExp")
End If
numsOnly = vbNullString
With rgx
.Global = True
.MultiLine = False
.Pattern = "[0-9]{1,7}"
If .Test(str) Then
Set cmat = .Execute(str)
'resize the nums array to accept the matches
ReDim nums(cmat.Count - 1)
'populate the nums array with the matches
For n = LBound(nums) To UBound(nums)
nums(n) = cmat.Item(n)
Next n
'convert the nums array to a delimited string
numsOnly = Join(nums, delim)
End If
End With
End Function
Regexp option that uses Replace
Sub Test()
Debug.Print StrOut("MyName;#123;#YourName;#3456;#HisName;#78")
End Sub
function
Option Explicit
Function StrOut(strIn As String) As String
Dim objRegex As Object
Set objRegex = CreateObject("vbscript.regexp")
With objRegex
.Pattern = "(^|.+?)(\d{1,7})"
.Global = True
If .Test(strIn) Then
StrOut = .Replace(strIn, "$2, ")
StrOut = Left$(StrOut, Len(StrOut) - 2)
Else
StrOut = "Nothing"
End If
End With
End Function
in the following examples I need to get the words on either side on the period
I am using this regex
Dim myRegex As New Regex("[^\w]+")
Dim mymatch As String() = myRegex.Split(currentField)
where as currentfield = one of the following 3 samples
Contacts.Address2 as `Contact Address2`
Contacts.ContactID
CONCAT(Contacts.FirstName;;' ';;Contacts.LastName) as `Contact`
returns are as follows.
1-- Contacts, Address2, as, Contact and Address2 do not want the word as.
2-- Contacts and ContactID this is ok.
3-- CONCAT,Contacts,FirstName,Contacts,LastName,as and Contact.
3rd one this is too much do not want CONCAT,as or Contact. I only want the four words (ones before and after the period) to be returned Contacts, Firstname, Contacts, and Lastname
how can I write the regex to only get words before and after the period
I would consider matching vs. splitting the input:
For Each m As Match In Regex.Matches(input, "(\w+)\.(\w+)")
Console.WriteLine(
String.Join(", ",
m.Groups(1).Value,
m.Groups(2).Value
))
Next
This is an example, It's not clear what you expect to do with the returned results.
Ideone Demo
I think you are looking to only split inside round brackets and you are not interested in the word as. Thus, I suggest a 2 step approach:
Get the substring(s) in round brackets (\([^()]+\) regex)
If there are such substrings, split them with the split regex, if not, split the original string with split regex (\W+|\s*\bas\b\s* regex).
Sample code:
'Dim currentField As String = "Contacts.Address2 as `Contact Address2`"
Dim currentField As String = "CONCAT(Contacts.FirstName;;' ';;Contacts.LastName) as `Contact`"
'Dim currentField As String = "Contacts.ContactID"
Dim myRegex As New Regex("\([^()]+\)")
Dim splitRegex As New Regex("\W+|\s*\bas\b\s*")
Dim mymatch As MatchCollection = myRegex.Matches(currentField)
If mymatch.Count > 0 Then
For Each match As Match In mymatch
Dim mysubstrs As String() = splitRegex.Split(match.Value)
For Each substr As String In mysubstrs
If String.IsNullOrEmpty(substr) = False Then
Console.WriteLine(substr)
End If
Next
Next
Else
Dim mysubstrs As String() = splitRegex.Split(currentField)
For Each substr As String In mysubstrs
If String.IsNullOrEmpty(substr) = False Then
Console.WriteLine(substr)
End If
Next
End If
here is the final working routine, based on the accepted answer above
Public Sub Load_Field_List(FieldSTR As String, FieldType As String)
Dim t As New FileIO.TextFieldParser(New System.IO.StringReader(FieldSTR))
t.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
t.Delimiters = New String() {","}
Dim currentRow As String()
Dim dr As DataRow
Dim ColListSTR As String = loadeddataview.Tables(0).Rows(0).Item("ColumnList")
Dim ColListSTRArr As String() = ColListSTR.Split(",")
While Not t.EndOfData
Try
currentRow = t.ReadFields()
Dim currentField As String 'field string
For Each currentField In currentRow
Dim startName As Integer
Dim endName As Integer
Dim name As String
dr = fieldDT.NewRow
Dim isValid As Boolean = False
If currentField = "" Then 'make sure current field has data
isValid = False
ElseIf (Regex.IsMatch(currentField, "(\w+)\.(\w+)")) = True Then 'make sure current field has xxxx.yyyy pattern
Dim m As Match = Regex.Match(currentField, "(\w+)\.(\w+)") 'sets m to the first xxxx.yyyy pattern
dr("Table") = m.Groups(1).Value 'sets table column to table name xxxx
dr("Column Name") = "`" & m.Groups(2).Value & "`" 'sets column name to column yyyy enclosed in ` `
If ColListSTRArr.Contains(m.Groups(2).Value) Then 'checks columnlist str to see if column visible
dr("Show") = "True"
Else
dr("Show") = "False"
End If
' this section overrides column name if it was set using AS `zzzzz` statement
startName = currentField.IndexOf("`")
endName = currentField.IndexOf("`", If(startName > 0, startName + 1, 0))
If (endName > startName) Then
Dim mylength As Integer = currentField.Length
name = currentField.Substring(startName, endName - startName + 1)
dr("Column Name") = name 'set override columname
dr("Field") = currentField.Substring(0, startName - 4) 'sets field minus the " as 'ZZZZZ" above
If ColListSTRArr.Contains(currentField.Substring(startName + 1, endName - startName - 1)) Then 'dup may be able to remove
dr("Show") = "True"
Else
dr("Show") = "False"
End If
Else
dr("Field") = currentField 'sets field if there was no " as `ZZZZZZ`" in string
End If
If FieldType = "Field" Then 'sets the column linking field
dr("Linking") = "No Linking"
Else
dr("Linking") = FieldType
End If
End If
' commit changes
fieldDT.Rows.Add(dr)
fieldDT.AcceptChanges()
DataGridView3.DataSource = fieldDT
DataGridView3.ClearSelection()
Next
Catch ex As Microsoft.VisualBasic.
FileIO.MalformedLineException
MsgBox("Line " & ex.Message &
"is not valid and will be skipped.")
End Try
End While
End Sub
I'm trying to make a vb function that takes as input a String and returns, if exist, the string made of numeric digits from the beginning until the first non numerical char, so:
123 -> 123
12f -> 12
12g34 -> 12
f12 -> ""
"" -> ""
I wrote a function that incrementally compares the result matching the regex, but it goes on even on non numeric characters...
This is the function:
Public Function ParseValoreVelocita(ByVal valoreRaw As String) As String
Dim result As New StringBuilder
Dim regexp As New Regex("^[0-9]+")
Dim tmp As New StringBuilder
Dim stringIndex As Integer = 0
Dim out As Boolean = False
While stringIndex < valoreRaw.Length AndAlso Not out
tmp.Append(valoreRaw.ElementAt(stringIndex))
If regexp.Match(tmp.ToString).Success Then
result.Append(valoreRaw.ElementAt(stringIndex))
stringIndex = stringIndex + 1
Else
out = True
End If
End While
Return result.ToString
End Function
The output always equals the input string, so there's something wrong and I can't get out of it...
Here's a LINQ solution that doesn't need regex and increases readability:
Dim startDigits = valoreRaw.TakeWhile(AddressOf Char.IsDigit)
Dim result As String = String.Concat(startDigits)
Try this instead. You need to use a capture group:
Public Function ParseValoreVelocita(ByVal valoreRaw As String) As String
Dim result As New StringBuilder
Dim regexp As New Regex("^([0-9]+)")
Dim tmp As New StringBuilder
Dim stringIndex As Integer = 0
Dim out As Boolean = False
While stringIndex < valoreRaw.Length AndAlso Not out
tmp.Append(valoreRaw.ElementAt(stringIndex))
If regexp.Match(tmp.ToString).Success Then
result.Append(regexp.Match(tmp.ToString).Groups(1).Value)
stringIndex = stringIndex + 1
Else
out = True
End If
End While
Return result.ToString
End Function
The expression:
Dim regexp As New Regex("^([0-9]+)")
and the result appending lines have been updated:
result.Append(regexp.Match(tmp.ToString).Groups(1).Value)
You have made your code very complex for a simple task.
Your loop keeps trying to build a longer string and it keeps checking if it is still working with digits, and if so keep appending results.
So and input string of "123x" would, if your code worked, produce a string of "112123" as output. In other words it matches the "1", then "12", then "123"and concatenates each before exiting after it finds the "x".
Here's what you should be doing:
Public Function ParseValoreVelocita(valoreRaw As String) As String
Dim regexp As New Regex("^([0-9]+)")
Dim match = regexp.Match(valoreRaw)
If match.Success Then
Return match.Groups(1).Captures(0).Value
Else
Return ""
End If
End Function
No loop and you let the regex do the work.
In VB.NET, I would like to increment a number in a string and have it zeroed filled.
Here is the sample string with the 5 digit number:
R00099
What I would like returned after incrementing it by one:
R00100
No need for PadLeft:
Dim result = String.Format("R{0:D5}", number)
The D5 part in the formatter will format the number as a decimal number, using a fixed number of five digits, and filling the redundant digits with zeros.
More information can be found on the MSDN article about the decimal format specifier.
If the strings have been validated and are in the form specified then this should work
Private Function add1ToStringNoChecking(theString As String) As String
'assumes many things about the input instring
Return String.Format("{0}{1:d5}", _
"R", _
CInt(theString.Substring(theString.Length - 5, 5)) + 1)
End Function
Private Sub Button1_Click(sender As System.Object, _
e As System.EventArgs) Handles Button1.Click
Dim testS As String = "R00009"
Debug.WriteLine(add1ToStringNoChecking(testS))
End Sub
Assuming (with the regex tag) that you want to strip the number out first, and the input will always be in the form of letters followed by numeric then:
Function Increment(ByVal prefixedNumber As String) As String
Dim result As String = String.Empty
Dim numericRegex As New Text.RegularExpressions.Regex("^(\D*)(\d*)")
Dim numericMatch As Text.RegularExpressions.Match = numericRegex.Match(prefixedNumber)
If numericMatch.Success Then
Dim number As Integer
If Integer.TryParse(numericMatch.Groups(2).Value, number) Then
result = String.Format("{0}{1:D5}", numericMatch.Groups(1).Value, number + 1)
Else
' throw a non parse exception.
End If
Else
' throw a non match exception.
End If
Return result
End Function
Have a look at the Regex and Integer.TryParse documentation
Here is a handy function to accomplish the OP requirement:
Public Function Counter(ByVal StartingNumber As Int32, ByVal IncrementValue As Int32, ByVal TotalNumberLength As Int32, ByVal Prefix As String) As String
Dim Temp As Int32 = StartingNumber + IncrementValue
Dim Temp2 As String = CStr(Temp)
Line50:
If Temp2.Length < TotalNumberLength Then
Temp2 = "0" & Temp2
GoTo Line50
ElseIf Temp2.Length = TotalNumberLength Then
'do nothing
Else
'means error
Throw New System.Exception()
End If
Return Prefix & Temp2
End Function
Example of using the function:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'now test the function
MessageBox.Show(Counter(99, 1, 5, "R"))
'it will show R00100
End Sub
NOTE: This solution has been tested OK with Visual Studio 2010.