Global change to all procedures?

We have a zillion procedures that access a text file which lives in a network-shared folder. The folder path is going to change soon (new computer setup) and I’m wondering if there is any way to update those procedures other than one at a time…


This should take each open database in turn and then go through each of their procedures and change the old path into the new path. It will automatically skip the Panorama Help file if it is open.

let theProc=""
let oldPath=""
let newPath=""
gettext "Enter old path…",oldPath
gettext "Enter new path…", newPath
looparray info(“files”), cr(), theDB, index
    if theDB <> "Panorama Help"
        looparray dbinfo("procedures",theDB),cr(),theProc,index
            setprocedureoptions theDB, theProc, "SOURCE", 
                replacewordexact(getproceduretext(theDB, theProc), oldPath, newPath)

If you want to hard code the path names instead of using the gettext to have them entered you would be wise to run this from a separate file and add a trap to skip the executing database. In this case you would change the line:

if theDB <> "Panorama Help"


if theDB <> "Panorama Help" and theDB <> info(“databasename”)

I have only minimally tested this so be careful.

I’ll give it a whirl!

Thanks a million.


In case you run into this again, maybe you should write the path in a variable that is defined on initialization. The new path in Gary’s procedure could change it to the variable.

Just tried it on one database and it appears to work perfectly. You’re the man!


The ability to programmatically edit multiple procedures in multiple databases is a very powerful, yet potentially hazardous feature of PanX. I don’t think it had been discussed in this forum before.

When PanX was first released I gathered my few hundred Pan6 files, opened then saved them as PanX files, and started browsing thru their resulting view menus. I was pleased to see much seemed to convert ok. Many of the exceptions were where syntex for a handful of statements/functions had changed slightly from Pan6 to PanX (eg. Change "a" "b" was fine in Pan6, Change "a","b" was required by PanX) or were listed under Help’s “Unimplemented Statements and Functions” list.

Usually I had many cases of each exception. Rather than manually repeat simple fixes a zillion times I dreamt of an automated macro fix. Help showed PanX had the required tools and I was familiar enough from Pan6 to code crude loop procedures. If I could devise a Replace statement, or one of its variants, specific for my syntex error and its needed fix, I could write a procedure in a separate db that would loop run it on every procedure in a given db. Moreover I could easily produce a return delimited text array list of given db paths into the procedure via drag and drop and loop that to feed the former. That code, alas since discarded, certainly wasn’t as polished as Gary’s example, but it worked and I could write and test it quickly enough to be worth my time savings over repeated manual fixes. Thus I could batch my repeated simple Pan6 to PanX fixes in my initial week working with PanX. It wouldn’t be worthwhile over a few manual fixes, but other Pan6 coders (I’m merely an experienced amateur) with many instances of needed identical replaceable conversion fixes may wish to try it.

Gary’s code is pretty much exactly how I would have written this. However, I would suggest one small change, from

if theDB <> "Panorama Help"


if dbinfo("folder",theDB) notcontains "/"

With this change, the loop will skip not only Panorama Help, but any other built in wizard that might happen to be open, for example the Import or Export wizard, or Favorite Databases, Formula Wizard, Font Awesome wizard, etc.

I couldn’t agree more, potentially very very hazardous. Obviously I could have built code like Gary’s into a Panorama wizard, but I think a feature like that would be far too dangerous. It could be possible to write a safer version that would display the changes about to be made and allow you to verify them, but that would take far more than the handful of code used by Gary’s solution. If you use Gary’s code, please be very careful that you enter text that you know for sure will only match the text you want to replace. Barry’s example of replacing a full path is probably safe, but replacing something shorter, like a field or variable name, could be a disaster if the field or variable name could possibly be embedded in some longer text in the source code.

Gary is indeed the man! I really appreciate that folks like Gary and David have so carefully studied the documentation.

Thank you for additional suggestions. It occurs to me, now that I’m beginning the process of changing the path from old to new shared volume, to wonder if there is an easy way to search for particular text across a batch of procedures without having to open them? If I have, say, 200 databases in one folder, is there a way to hunt for the text “Company_Shared_Data” and list the ones that have that text in one or more procedures?

thanks again,

I have a need for exactly this sort of search so I’m now writing a complete general solution - be patient.

The Open View window will do a search like this thru all open databases. See Searching Full Source on this help page.

If you have 200 databases in a folder, you probably won’t have all 200 open at one time. It is certainly possible to write a program that would open each database in turn and search each one, which I imagine is what Michael has in mind. I’ll give a hint – such a procedure should probably use the views( function, which would be much faster than using getproceduretext( for each individual procedure (though that would also work, just more slowly). The Open View window mentioned above uses the views( function to do searching.

I tried using the views( function without success - Jim, you might suggest how I could use it. My first draft is here:

Comments and criticism are welcome.

My comments aren’t in any particular order.

Aren’t you closing the database in the wrong loop? It looks to me like you are closing it after examining the first procedure in the database, which isn’t going to work too well when you try to look at the second procedure in the database.

Also, you might want to check to see if a database is already open, and if so, leave it open.

I would also recommend allowing the user to cancel the choosefolderdialog and gettext dialogs.

Minor niggle – checking for

bkFileName notcontains "/"

isn’t going to hurt anything, but it will never be true since choosefiledialog won’t select a folder inside a bundle. I made that suggestion for checking whether an open database was part of Panorama, but in this case you’re not going off the list of open files.

Since you want to generate a report that shows the line number of multiple occurrences of the search text, the views( function isn’t going to work for you. So you’re fine with the getproceduretext statement. I like your idea of generating a report, but instead of copying it into the clipboard, I would suggest using the displaydata statement.

I wonder if it would be easier to start by adding line numbers to every line in the code, then do an arrayfilter to search for the text. Then you wouldn’t have a problem with duplicate lines as you do now, and I think the logic would be a bit simpler also. In fact I think you could do it all in one go.

getproceduretext bkFileName,theProc,bkProcText
arrayfilter bkProcText,bkProcLines,cr(),?(import() contains bkSearchString,"    Line "+seq()+": "+strip(import()),"")

Then you can skip most of your following logic, bkProcText is ready to put into the report. I haven’t tested it but it should be close.

That’s all helpful - thanks Jim. I’ll look at it tomorrow (dinner time here).

I couldn’t not look at this comment because my code does pick up all procedures in every file.

I can, in fact, replace the opensecret code at around line 48 with this:

    opensecret bkFolderName +"/"+ bkFileName
    closedatabase bkFileName
    bkReportList = bkReportList +cr()+cr()+ "  File: " + bkFileName

and it still works! Is that a bug or a feature?

Open View… works well for searching smallish numbers of open dbs. It can’t search closed ones and chokes on large numbers of open dbs. My alternative solution, partially external to PanX, is a short procedure ListProcs added to my dbs which exports all their Procedures to a text file in a set folder.

I run ListProcs in that db whenever I change a procedure, building an ersatz db of all my code over time. BBEdit’s free version or their older TextWrangler, offers “Multi-File Search…” which can search entire folders on disk promptly. So I can search all my procedures so listed, open or closed, at once.

local ProcedureText
looparray dbinfo("Procedures",""),¶,element
    if element≠"ListProcs"
        ProcedureText=ProcedureText+"§§§§ For Procedure: "+element+ " the source text is:"+¶+getproceduretext("",element)+¶+¶+¶
if ProcedureText≠""
    filesave "PATH_TO_YOUR_FOLDER"+"/P§ "+info("databasename")+".txt", ProcedureText

Just drag in your own storage folder path to replace my placeholder and the code is ready to use. Name it ListProcs and it will export every procedure in that db, not named ListProcs, to the folder you specified. Using View Organizer… it can easily be copied to multiple dbs.

While I was asleep, Gary Yonaites kindly coded all of Jim’s suggestions and a couple of his own - a bit like The Elves and the Shoemaker.

The final product is available at

I hope this does what you want Barry.

You folks are beyond amazing. I feel like the guy who drops by Rockler’s to buy a nice chisel and comes home with a $30,000 CNC machine. (Except in this case I don’t have to explain to anybody where I got the new toy.)

I’ll give it a whirl tomorrow!



Very nice Gary. I don’t have any idea what The Elves and the Shoemaker is though.

For future reference, I believe you could replace this code on line 61:

("        " + str(seq()))[-7,-1]

with this:


In my opinion it’s not worth re-uploading, both of these work the same, but if you ever need to do this again I think the fixedwidthright( function is easier to remember and understand. (In fact, the fixedwidthright( function actually internally uses the text funnel technique you used to perform the operation.)

It’s one of the Brothers Grimm more famous stories - check it out on Wikipedia.

Re the alternative code you suggested, if the fixedwidthright( function does what my code does, I’ve actually saved a step :slight_smile:

Don’t hesitate to let us know if it doesn’t do what you want.