Replace multiple patterns in file using callbacks

From NSIS Wiki
Jump to navigationJump to search
Author: bazzargh (talk, contrib)


Description

There's other versions of find and replace that allow you to find and replace a single string in a file, but they don't deal with multiple replacements on a single line, or replacements of multiple patterns. I've written an alternative that reads and writes a file, calling a supplied function for every line, so you can make that function do the multiple replacements; thus doing all your replacements in a single pass.

Its a little faster than doing multiple passes with AdvReplaceInFile, further improvements in find & replace for individual strings will make it faster still.

Example Usage

GetFunctionAddress $R0 ReplaceInSQL ; handle to callback fn
Push $R0
Push "$INSTDIR\template.sql" ; file to replace in
Call ReplaceInFile

Where ReplaceInSQL (or any function of your choosing) looks like:

Function ReplaceInSQL
	; save R1
	Push $R1
	Exch
	; A sequence of replacements.
        ; the string to replace in is at the top of the stack
	Push "@@foo@@" ; string to find
	Push "bar" ; string to replace it with
	Call StrRep ; see elsewhere in NSIS Wiki
        ; the string to replace in is at the top of the stack again
	Push "@@tellytubby@@" ; string to find
	Push "againagain" ; string to replace it with
	Call StrRep 
        ; and so on
	; restore stack
	Exch 
	Pop $R1
FunctionEnd

The Function

Function ReplaceInFile
	Exch $R0 ;file name to search in
	Exch 
	Exch $R4 ;callback function handle
	Push $R1 ;file handle
	Push $R2 ;temp file name
	Push $R3 ;temp file handle
	Push $R5 ;line read
 
	GetTempFileName $R2
  	FileOpen $R1 $R0 r ;file to search in
  	FileOpen $R3 $R2 w ;temp file
 
loop_read:
 	ClearErrors
 	FileRead $R1 $R5 ;read line
 	Push $R5 ; put line on stack
 	Call $R4
 	Pop $R5 ; read line from stack
 	IfErrors exit
 	FileWrite $R3 $R5 ;write modified line
	Goto loop_read
exit:
  	FileClose $R1
  	FileClose $R3
 
   	SetDetailsPrint none
  	Delete $R0
  	Rename $R2 $R0
  	Delete $R2
   	SetDetailsPrint both
 
	; pop in reverse order
	Pop $R5
	Pop $R3
	Pop $R2
	Pop $R1
	Pop $R4
	Pop $R0
FunctionEnd