0

I'm writing an Excel VBA program to take measurements of equipment and update values at various readings. Here is a brief sample of what my file looks like:

[11904]
400: 0.4
500: 0.3
600: 3.3

[11905]
400: 1.0
500: 2.0
600: 3.0

The number in the brackets is the S/N of the equipment being used, the big number is the measurement and the number after the colon is the equipment's offset value. What I want to do is write something that will locate the S/N, locate the measurement value, then overwrite the offset value. The .ini file has A LOT of S/Ns that all take the same measurement but have different offsets. Here is some demo code I've tried from Spreadsheet Guru:

Private Sub CommandButton1_Click()
'PURPOSE: Modify Contents of a text file using Find/Replace
'SOURCE: www.TheSpreadsheetGuru.com

Dim TextFile As Integer
Dim FilePath As String
Dim FileContent As String

'File Path of Text File
FilePath = "C:\Temp\test.ini"

'Determine the next file number available for use by the FileOpen function
TextFile = FreeFile

'Open the text file in a Read State
Open FilePath For Input As TextFile

'Store file content inside a variable
FileContent = Input(LOF(TextFile), TextFile)

'Clost Text File
Close TextFile

'Find/Replace
FileContent = Replace(FileContent, "[HEADER TEST]", "[HEADER TEST]")
FileContent = Replace(FileContent, "Inserting new line", "Replacing line")
FileContent = Replace(FileContent, "Blah blah blah", "replaced this line too!")

'Determine the next file number available for use by the FileOpen function
TextFile = FreeFile

'Open the text file in a Write State
Open FilePath For Output As TextFile

'Write New Text data to file
Print #TextFile, FileContent

'Clost Text File
Close TextFile
End Sub

The code works, but it updates anything that says "Inserting new line" and "blah blah blah." I was hoping it would only replace one occurrence once I had it find the "[HEADER TEST]."

My issue is two-fold:

How do I only change, say, measurement "400" for just one S/N in the file?

Also, once I locate text I want to change, how do I only write the offset value instead of the entire string?

If I'm able to successfully locate a line and only edit one line, I can just replace the entire string if need be. I cannot change the .ini's format, as we use a program that reads it.

0

3 Answers 3

1

To replace only first occurrence you should use combination of StrPos, Left and Mid functions:

if strpos(FileContent, "blabla") > 0 then 
    contentBeforeMatch = Left(FileContent, strpos(FileContent, "blabla") -1)
    contentAfterMatch = Mid(FileContent,  strpos(FileContent, "blabla") + Len("blabla") - 1))
    FileContent = contentBeforeMatch & "New Value" & contentAfterMatch
end if
Sign up to request clarification or add additional context in comments.

4 Comments

error returned that "strpos" is not defined. This isn't a default function in Excel VBA?
Edit: Replaced in InStr, seems to work...need to play with it a bit more to do what I want. My first attempt got rid of the header and I want the head to remain, just replace one or two lines beneath it. Is there a way to replace an entire block of text? For my application, I'm looking to replace over 20 lines of code. Thanks!
So, this is all about using string functions - you need to define starting and ending expressions for your block. Then you just search for the start, take everything before it (with Left() funct) then search for the end (do not forget to specify start position past start block), then you just take everything past end block. Then you just concatenate everything before start block, new block and everything past end block. That's it :)
Awesome! Tweaked it a bit and got it to do what I want. Thanks :)
1

You might consider using Filter, Split, and Join to isolate the area you want to change. Here's an example

Sub ReplaceOffset(ByVal sHead As String, ByVal sMeasure As String, ByVal dValue As Double)

    Dim sFile As String, lFile As Long
    Dim vaLines As Variant
    Dim vaMeasures As Variant
    Dim sOld As String, sNew As String, sOldMeas
    Dim i As Long

    lFile = FreeFile
    sFile = "C:\Temp\Test.ini"

    'Read in the file to an array
    Open sFile For Input As lFile
        vaLines = Split(Input$(LOF(lFile), lFile), "[")
    Close lFile

    'Filter to find the right header
    sOld = Filter(vaLines, sHead & "]")(0)
    'Split the header into measurements
    vaMeasures = Split(sOld, vbNewLine)

    'Get the old value
    sOldMeas = Filter(vaMeasures, sMeasure & ":")(0)
    'Replace old With new
    sNew = Replace(sOld, sOldMeas, sMeasure & ": " & Format(dValue, "0.0"))

    'Replace the old With the new and write it out to the file
    lFile = FreeFile
    Open sFile For Output As lFile
        Print #lFile, Replace(Join(vaLines, "["), sOld, sNew)
    Close lFile

End Sub

You call it like

ReplaceOffset "11906","500",.1

It splits the original file on [ so that each header is it's own line. Then it filters that array on whatever header you send in but adds a ] to the end of it so there's no false matches.

Once it finds the right header, it splits that on vbNewLine so that each measure is its own array element. The it filters that array to find the right measure. The old measure it replaced with the new measure. Then the old header is replaced with the new header.

If you pass in stuff that's not in the file, you'll get an error. So you should build some error checking into it.

Update: Descending Measures

The above code assumes the Measures appear ascending in the file. If they are descending, you can use

    sOldMeas = Filter(vaMeasures, sMeasure & ":")(UBound(Filter(vaMeasures, sMeasure & ":")))

The Filter() function returns an array of a wildcard match of the array. If you search for 700, the returned array will contain 2700, 1700, and 700 (assuming they are all present). The Filter(...)(0) syntax returns the first element - that works for ascending. The Filter(...)(Ubound(Filter(...))) returns the last element - works if they're sorted descending.

Update: Unsorted Measures

This version introduces some special characters so that you make sure you're only replacing an exact match of the Measures string

Sub ReplaceOffset(ByVal sHead As String, ByVal sMeasure As String, ByVal dValue As Double)

    Dim sFile As String, lFile As Long
    Dim vaLines As Variant
    Dim vaMeasures As Variant
    Dim sOld As String, sNew As String, sOldMeas
    Dim i As Long

    lFile = FreeFile
    sFile = "C:\Temp\Test.ini"

    'Read in the file to an array
    Open sFile For Input As lFile
        vaLines = Split(Input$(LOF(lFile), lFile), "[")
    Close lFile

    'Filter to find the right header
    sOld = Filter(vaLines, sHead & "]")(0)
    sOld = Replace$(sOld, vbNewLine, vbNewLine & "~")

    'Get the old value if Measures are unsorted
    vaMeasures = Split(sOld, vbNewLine)
    sOldMeas = Filter(vaMeasures, "~" & sMeasure & ":")(0)

    'Replace old With new
    sNew = Replace(sOld, sOldMeas, sMeasure & ": " & Format(dValue, "0.0"))
    sNew = Replace(sNew, vbNewLine & "~", vbNewLine)
    sOld = Replace(sOld, vbNewLine & "~", vbNewLine)

    'Replace the old With the new and write it out to the file
    lFile = FreeFile
    Open sFile For Output As lFile
        Print #lFile, Replace(Join(vaLines, "["), sOld, sNew)
    Close lFile

End Sub

It turns 2700:, 1700:, 700: into ~2700:, ~1700:, ~700: so that when you search for ~700:, you don't get 2700 regardless of the sort order.

3 Comments

Hi Dick, that looks like a cool. I'll have to give that a whirl. Thanks :)
Hi Dick, code works great aside for one hiccup. I am doing a range from 500 to 2700. All is good as i replace the values in the file for 2700 - 1000 (text file goes in highest to lowest unfortunately), but once i hit 900 - 500, the values for 1900, 1800, 2700, 2600 and 2500 are changed instead of 900 - 500 as expected. How do i modify the filter to change only if the EXACT string is present in the file after the header? Does the (0) after the filter function have any effect? I've never seen that format before for it...
@Alex I added some more code for descending and unsorted measures.
0

Another approche you could use Excel functionality (if you are already using Excel :) ),
Load -> Textfiles
Search -> values
Rewrite -> Textfile

But the Code would have to be optimized

Private Sub CommandButton1_Click()

    Dim NewValue As String
    Dim FilePath As String
    Dim Index As Integer
    Dim TextRow

    FilePath = "C:\Temp\test.ini"

    SearchValue = "[11905]"
    ChangeValue = "400"
    NewValue = "123"

    With ActiveSheet.QueryTables.Add(Connection:= _
        "TEXT;" + FilePath, Destination:=Range("$A$1"))
        .TextFileStartRow = 1
        .TextFileParseType = xlDelimited
        .TextFileOtherDelimiter = ":"
        .TextFileColumnDataTypes = Array(1, 1)
        .Refresh BackgroundQuery:=False
    End With

    ' search for key
    Cells.Find(What:=SearchValue, After:=ActiveCell, LookIn:=xlFormulas, _
        LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
        MatchCase:=False, SearchFormat:=False).Activate

    ' search for value to change
    Cells.Find(What:=ChangeValue, After:=ActiveCell, LookIn:=xlFormulas, LookAt:= _
        xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False _
        , SearchFormat:=False).Activate

    ' change Value
    ActiveCell.FormulaR1C1 = NewValue

    ' select bottom row start
    Range("A1").Select
    Selection.End(xlToRight).Select
    Selection.End(xlDown).Select
    Selection.End(xlToLeft).Select
    Selection.End(xlUp).Select
    ' select bottom row end

    ' select all rows
    Range(Range("A1"), Selection).Select

    ' write file
    Open FilePath For Output As #1

        'Write New Text data to file
        For Index = 1 To Selection.Rows.Count + 1
            TextRow = Selection.Cells(Index, 1).FormulaR1C1
            If InStr(1, TextRow, "[") = 0 And Not TextRow = "" Then
                TextRow = TextRow + ":" + Selection.Cells(Index, 2).FormulaR1C1
            End If
            Print #1, TextRow
        Next Index

    Close #1

End Sub

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.