VB Remove blocks of text in textfile - vb.net

Recently switched to vb after my time in C# and simple question using sytemIO. My predecessor wrote a package that generated error logs to a text file. The following is a sample:
2017-10-20 15:30:11.481
CmsMonitorService.exe, CmsMonitorService.UpdateCmsOffLine
OffLineUpdater error: Getting list of files stored on the off-line vault.
------------------------------
2017-10-20 15:31:11.547
CmsMonitorService.exe, CmsMonitorService.UpdateCmsOffLine
OffLineUpdater error: Creating folder 'OffLineUpdates' (it may already exist checkHost)
at CmsMonitorService.CmsMonitorService.UpdateCmsOffLine(Object[] Args)
------------------------------
2017-10-20 15:31:11.547
CmsMonitorService.exe, CmsMonitorService.UpdateCmsOffLine
OffLineUpdater error: Creating folder
------------------------------
But this is killing the machines. What the code currently does when it is writing is to removed the contents line by line which is painfully slow. It uses the following:
Do
If allLines.Count = 0 Then
Exit Do
ElseIf allLines(0).StartsWith("-----") Then
allLines.RemoveAt(0)
Exit Do
Else
allLines.RemoveAt(0)
End If
Loop
There can be thousands of these (they are at various locations).
What I had wanted to do is find a way of removing the blocks bewteen the dashes.
Thanks for any ideas everyone.....
Gareth

Here's a quick example of the solution I described in my comment above:
Imports System.Text
Module Module1
Sub Main()
Dim s = "keep line 1" & Environment.NewLine &
"keep line 2" & Environment.NewLine &
"----------" & Environment.NewLine &
"remove line 1" & Environment.NewLine &
"remove line 2" & Environment.NewLine &
"----------" & Environment.NewLine &
"keep line 3" & Environment.NewLine &
"keep line 4" & Environment.NewLine &
"----------" & Environment.NewLine &
"remove line 3" & Environment.NewLine &
"remove line 4" & Environment.NewLine &
"----------" & Environment.NewLine &
"keep line 5" & Environment.NewLine &
"keep line 6" & Environment.NewLine
Dim sb As New StringBuilder(s)
Dim endIndex = s.LastIndexOf("----------")
Do While endIndex <> -1
Dim startIndex = s.LastIndexOf("----------", endIndex - 1)
Dim substring = s.Substring(startIndex, endIndex - startIndex + 12) 'Add the length of the delimiter and the line break.
'Remove the delimited block from the StringBuilder.
sb.Replace(substring, String.Empty, startIndex, substring.Length)
endIndex = s.LastIndexOf("----------", startIndex - 1)
Loop
Console.WriteLine("Before:")
Console.WriteLine(s)
Console.WriteLine("After:")
Console.WriteLine(sb.ToString())
Console.ReadLine()
End Sub
End Module
It might depend on the specifics of the text as to whether that is more efficient or just using a String alone is.

Related

MS Access VBA Open a Text file and write to a specific line without overwriting the file

I have a text file that I would like to add a header and a footer to. I don't want to overwrite the first or last lines, rather I'd like to add a new first line and append a line to the end of the file.
The below Function works for appending to the bottom of the file but I'd like to be able to control where the line is inserted. Thank you!
Function WriteToText(sFile As String, sText As String)
On Error GoTo Err_Handler
Dim iFileNumber As Integer
iFileNumber = FreeFile ' Get unused file number
Open sFile For Append As #iFileNumber ' Connect to the file
Print #iFileNumber, sText ' Append our string
Close #iFileNumber ' Close the file Exit_Err_Handler:
Exit Function Err_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Txt_Append" & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, "An Error has Occured!"
GoTo Exit_Err_Handler End Function
What you do for a task like this:
Read the whole file into a string (Open For Input)
Add the data you want: S = "header line" & vbCrLf & S & vbCrLf & "footer line"
Write the whole string to the file, overwriting it (Open For Output)

VBA execute code in string

I am trying to execute vba code that is inside a string WITHOUT writting the code in a temp file.
For exemple :
Dim code As String
code = "n = 0 : e_i_e = 0 : For e_i_e = 0 To 100 : n+=1 : Next"
I have tried Eval, Evaluate, Run, executeGlobal and adding a new module with
Set VBComp = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_StdModule)
VBComp.Name = "NewModule"
Set VBCodeMod = ThisWorkbook.VBProject.VBComponents("NewModule").CodeModule
With VBCodeMod
LineNum = .CountOfLines + 1
.InsertLines LineNum, _
"Sub MyNewProcedure()" & Chr(13) & _
code & Chr(13) & _
"End Sub"
End With
Application.Run "MyNewProcedure"
but all of these are returning errors ='(.
Thank you !
You cannot break in code that's been generated after you've compiled your project, so you need to make sure you build that dynamic module with valid, compilable code.
You know before you hit that F5 button that your code is going to look like this:
Sub MyNewProcedure()
n = 0 : e_i_e = 0 : For e_i_e = 0 To 100 : n+=1 : Next
End Sub
Why not just take that snippet and paste it somewhere and see what the VBE complains about?
Wow. See, this is why cramming half a dozen instructions on the same line of code is a bad idea - if it was one instruction per line you wouldn't be wondering which one is broken.
As was already mentioned, n += 1 is not VBA syntax (it's not specifically C# syntax either); incrementing a value in VBA needs to access the current value, so n = n + 1.
It's not clear where n and e_i_e are coming from. If both are locals, then your procedure accomplishes essentially nothing. If n is declared outside MyNewProcedure, then you should consider passing it as a ByRef parameter, or better, leaving it out completely and making a Function with the result of which the calling code assigns n to.
Sub MyNewProcedure(ByRef n As Long)
Dim i As Long
For i = 0 To 100
n = i
Next
End Sub
Which boils down to:
Function MyNewFunction() As Long
MyNewFunction = 100
End Function
Which makes me wonder what the heck you're trying to accomplish.
If there is a bug in your generated code, you're going to have to debug it in a string, because the VBE's debugger won't let you break on generated code - this means it's crucially important that you generate code in a readable and maintainable way. There's currently nowhere in your code where you have the actual full generated code in a clear string - it's concatenated inline inside the InsertLines call.
Consider:
Dim code As String
code = "'Option Explicit" & vbNewLine & _
"Public Sub MyNewProcedure()" & vbNewLine & _
" n = 0" & vbNewLine & _
" e_i_e = 0" & vbNewLine & _
" For e_i_e = 0 To 100" & vbNewLine & _
" n = n + 1 ' fixed from n += 1" & vbNewLine & _
" Next" & vbNewLine & _
"End Sub" & vbNewLine
'...
'Debug.Print code
.InsertLines LineNum, code
It's much easier to get the full code back while debugging, and much easier to review and fix as well. Note that there's a limit to how many line continuations you can chain though.
Your code is c# addition, it needs to be n=n+1
You can create a module and populate it with a sub from a string. In fact one of the way developers place their vba code into a repository is to do just that: extract the code from modules as strings and then read them back in from whatever version control software they're using.
Here's a full example to do what you're looking to do (assuming you want your sub in a new separate module):
Sub make_module_sub_and_run():
Dim strMacro As String
Dim myModule As VBComponent
Set myModule = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_StdModule)
strMacro = "Public Sub exampleSub()" & vbCrLf & _
" n=0" & vbCrLf & _
" For e_i_e = 0 to 100:" & vbCrLf & _
" n=n+1" & vbCrLf & _
" Next" & vbCrLf & _
" MsgBox(""Hello World. Oh and n="" & n)" & vbCrLf & _
"End Sub"
myModule.CodeModule.AddFromString strMacro
Application.Run myModule.Name & ".exampleSub"
End Sub
Note that what should happen as you type "vbext_ct_StdModule" is that excel intellisense will note that this is missing and will ask whether you want to load it in - which you, of course, do.
Also note that I've deliberately prefixed the sub with the module name when running it - otherwise if you were to run it repeatedly you'll create new modules with a sub of the same name and excel wouldn't know which one to run.

Build tools in Excel which do not need an end user installation

Is it possible to build tools/add-in's in Microsoft Excel which do not need an end user installation.
For example we use VBA to build excel based tools but the language is very basic. Are there any other technologies similar to VBA or VSTO ..?
Thanks,
Jayanth
No. (although Admins can do it for users) But all the user has to do is double click something (excel sheet, word document, batch file, vbsfile, jscript, exe (type iexpress in Start - Run).
It's called Visual Basic so yes it is Basic. But the language is simple to write and is anything but basic in capabilities. It can do anything that any other language can do. With COM it can do in 10 lines that takes a C programmer hundreds. Anything any other language can do so can VB/VBA, usually in far less lines.
Perhaps you should think about your questions.
Number 1 is a favourite of those wanting to write viruses, that's why it's impossible to install code without someone's permission. Depending on circumstances, it may be against the law.
Number 2, I suspect you really don't know how to use VBA. Remember there is VBA and Excel and they are separate. If you've been programming Excel you probably haven't learnt much VBA, more likely you've learnt Excel's object model.
Remember VB6 and VBA is same language (and vbscript is pasteable into both VB6/VBA) hosted differently. If looking for solutions look at VB6/VBA/VBScript. It's a cinch to write a basic spreadsheet in VBA/VB6/VBScript.
VB6/VBA are easy COM programs and their are thousands of objects to play with. Can call API calls so can do anything.
Here's a program that randomises lines in a text file. It is VBScript and adodb (the main windows database object). It creates a database in memory, adds a random number and the line of text, sorts it, writes the database out.
Sub Randomise
Randomize
Set rs = CreateObject("ADODB.Recordset")
With rs
.Fields.Append "RandomNumber", 4
.Fields.Append "Txt", 201, 5000
.Open
Do Until Inp.AtEndOfStream
.AddNew
.Fields("RandomNumber").value = Rnd() * 10000
.Fields("Txt").value = Inp.readline
.UpDate
Loop
.Sort = "RandomNumber"
Do While not .EOF
Outp.writeline .Fields("Txt").Value
.MoveNext
Loop
End With
End Sub
Or a script to speak lines of text.
Sub Speak
Set objVoice = CreateObject("SAPI.SpVoice")
Do Until Inp.AtEndOfStream
Line=Inp.readline
outp.writeline Line
objVoice.Speak Line
Loop
End Sub
Or perhaps you would like to add a macro language to your projects. This runs a vbs script specified on a command line against every line of of a file. This includes syntax checking etc,
Sub VBSCmd
RawScript = Arg(1)
'Remove ^ from quoting command line and replace : with vbcrlf so get line number if error
Script = Replace(RawScript, "^", "")
Script = Replace(Script, "'", chr(34))
Script = Replace(Script, ":", vbcrlf)
'Building the script with predefined statements and the user's code
Script = "Dim gU" & vbcrlf & "Dim gdU" & vbcrlf & "Set gdU = CreateObject(" & chr(34) & "Scripting.Dictionary" & chr(34) & ")" & vbcrlf & "Function UF(L, LC)" & vbcrlf & "Set greU = New RegExp" & vbcrlf & "On Error Resume Next" & vbcrlf & Script & vbcrlf & "End Function" & vbcrlf
'Testing the script for syntax errors
On Error Resume Next
set ScriptControl1 = wscript.createObject("MSScriptControl.ScriptControl",SC)
With ScriptControl1
.Language = "VBScript"
.UseSafeSubset = False
.AllowUI = True
.AddCode Script
End With
With ScriptControl1.Error
If .number <> 0 then
Outp.WriteBlankLines(1)
Outp.WriteLine "User function syntax error"
Outp.WriteLine "=========================="
Outp.WriteBlankLines(1)
Outp.Write NumberScript(Script)
Outp.WriteBlankLines(2)
Outp.WriteLine "Error " & .number & " " & .description
Outp.WriteLine "Line " & .line & " " & "Col " & .column
Exit Sub
End If
End With
ExecuteGlobal(Script)
'Remove the first line as the parameters are the first line
'Line=Inp.readline
Do Until Inp.AtEndOfStream
Line=Inp.readline
LineCount = Inp.Line
temp = UF(Line, LineCount)
If err.number <> 0 then
outp.writeline ""
outp.writeline ""
outp.writeline "User function runtime error"
outp.writeline "==========================="
Outp.WriteBlankLines(1)
Outp.Write NumberScript(Script)
Outp.WriteBlankLines(2)
Outp.WriteLine "Error " & err.number & " " & err.description
Outp.WriteLine "Source " & err.source
Outp.WriteLine "Line number and column not available for runtime errors"
wscript.quit
End If
outp.writeline temp
Loop
End Sub
And a change of pace from objects to API calls.
This creates a Richtext control that was released after VB6 so isn't built in.
Ret = LoadLibrary("c:\windows\system32\MSFTEDIT.dll")
If Ret = 0 Then MsgBox "Load Lib " & Err.LastDllError
Flags = WS_CHILD + WS_HSCROLL + WS_VSCROLL + WS_VISIBLE + ES_MULTILINE + ES_AUTOHSCROLL + ES_AUTOVSCROLL + ES_NOHIDESEL + ES_WANTRETURN
Dim barray() As Byte
barray = "RICHEDIT50W" & vbNullChar
gRtfHwnd = CreateWindowEx(WS_EX_ACCEPTFILES + WS_EX_CLIENTEDGE, barray(0), "", Flags, 0, 0, ScaleX(Me.ScaleWidth, vbTwips, vbPixels), ScaleY(Me.ScaleHeight, vbTwips, vbPixels), Me.hWnd, vbNull, App.hInstance, vbNull)
Ret = SendMessageByVal(gRtfHwnd, EM_SETTEXTMODE, TM_MULTILEVELUNDO + TM_PLAINTEXT + TM_MULTICODEPAGE, 0)
If GetTextMode(gRtfHwnd) <> 41 Then MsgBox "get Text mode = " & GetTextMode(gRtfHwnd)
Ret = SendMessageByVal(gRtfHwnd, EM_SETEDITSTYLE, SES_ALLOWBEEPS + SES_USECRLF, SES_ALLOWBEEPS + SES_USECRLF)
Ret = SendMessageByVal(gRtfHwnd, EM_SETLANGOPTIONS, IMF_None, IMF_None)
If GetTextMode(gRtfHwnd) <> 41 Then MsgBox "get Text mode (2) = " & GetTextMode(gRtfHwnd)
Ret = SendMessageByVal(gRtfHwnd, EM_SETTYPOGRAPHYOPTIONS, TO_None, TO_None)
'Below is the default anyway with CreateWin flags spec above
Ret = SendMessageByVal(gRtfHwnd, EM_SETOPTIONS, ECO_AUTOHSCROLL + ECO_AUTOVSCROLL + ECO_NOHIDESEL + ECO_WANTRETURN, ECOOP_OR)
SetFocusAPI gRtfHwnd
Me.Show
Dim ParaFormat As ITextPara
Dim FontFormat As ITextFont
Ret = SendMessageAny(gRtfHwnd, EM_GETOLEINTERFACE, 0, TomObj)

Removing duplicated lines from a textbox and getting the count of the removed lines. (VB.NET)

I am developing a bar management software, i did the most of the job but now i don't know the code to remove the same lines from a textbox and getting the number of the removed lines after they are removed.
My code till now:
For Each saveitem As ListViewItem In Form1.ListView1.Items
RichTextBox1.AppendText(saveitem.Text & vbNewLine)
TextBox3.AppendText(saveitem.SubItems(1).Text & vbNewLine)
Next
RichTextBox1.AppendText(vbNewLine & vbNewLine & "TOTALI:" & " " & Form1.TextBoxX5.Text & vbNewLine & "TOTALI pa TVSH:" & " " & TextBox4.Text)
One possible way :
'get array of individual line of text'
Dim textLines = TextBox3.Text.Split(New String(){vbNewLine}, StringSplitOptions.RemoveEmptyEntries)
Dim totalCount = textLines.Length
Dim noDuplicateCount = textLines.Distinct().Count()
'store number of lines removed'
Dim removedLinesCount = totalCount - noDuplicateCount
'join distinct line of texts by new line into single string'
TextBox3.Text = String.Join(vbNewLine, textLines.Distinct())

Replacing 2x vbCrLf at once

I have a string in which I'm trying to replace all VbCr / VbLf with VbCrLf. This is in an attempt to scrape some HTML.
My code looks like this:
leHTML = leHTML.Replace(vbLf, vbCrLf)
leHTML = leHTML.Replace(vbCr, vbCrLf)
However in many cases I'm then left with 2x vbCrLf of which I only want 1.
leHTML = leHTML.Replace(vbCrLf & vbCrLf, vbCrLf)
The line above doesn't seem to be doing anything. How can I replace 2x vbCrLf with 1x vbCrLf? Is there a better way of going about "normalizing" Line Feeds and Carriage Returns?
You should not replace a correct vbCrLf in the first place. Instead replace only those characters where replacement is necessary. A handy tool for this task is a regular expression.
There are two cases that you want to get rid off:
vbCr with no following vbLf
the Regex for this is (vbCr)(?!vbLf)
vbLf with no preceeding vbCr
the Regex for this is (?<!vbCr)(vbLf)
Putting this together, we get the following regex:
Dim regex = New Regex("((" & vbCr & ")(?!" & vbLf & ")|(?<!" & vbCr & ")(" & vbLf & "))")
Throw this on your input and you're done:
leHTML = regex.Replace(leHTML, vbCrLf)
Here is a simple test program (vbCr and vbLf have been replaced by cr and lf respectively, so there is a visible output):
Dim str = "crlf cr cr lf crlf lf"
Dim regex = New Regex("((cr)(?!lf)|(?<!cr)(lf))")
str = regex.Replace(str, "crlf")
Console.WriteLine(str)
The result is:
crlf crlf crlf crlf crlf crlf
You're going to have to work a little harder at this. Instead of blindly replacing characters, you need to see what is there first, then determine what you are replacing. For example (this is NOT the complete code):
if leHTML.contains(vbcr) andalso leHTML.contains(vblf) then
leHTML = leHTML.Replace(vbCr & vbLf, vbCrLf)
elseif leHTML.contains(vbcr) then
leHTML = leHTML.Replace(vbCr, vbCrLf)
elseif leHTML.contains(vblf) then
leHTML = leHTML.Replace(vbLf, vbCrLf)
else
...
end
Probably this is a good pattern to use a Regex replace expression.
For example
Dim pattern = "(\r|\n)"
Dim search = "The" & vbCr & "Test string" & vbCr & _
"used as an" & vbLf & "Example" & vbCrLf & "."
Dim m = Regex.Replace(search, pattern, vbCrLf)
Console.WriteLine(m)
The first line prepare the pattern to search for using the C# syntax for vbCr=\r and vbLf=\n enclosing the two characters in an optional group (find a vbCr or a vbLf).
Then the replace method search one or the other char and replace it with the double vbCrLf character sequence.
But now we have a problem, the single vbCrLf present in the test string has been doubled, so you need another replace to remove the double sequence with just one vbCrLf
pattern = "\r\n\r\n"
m = Regex.Replace(search, pattern, vbCrLf)
Console.WriteLine(m)