Hi Nigel - after your terrific explanation the extra braces makes total sense, and that one small change made all the difference. Thanks so much!
I removed that one comment referring to a prior incarnation of the script in order to save others from also wondering what it meant, and I made one more change to the code, besides renaming some of the handlers to make it even easier to follow, which is:
I split the main() handler into two, singleColumnSearch() and multipleColumnSearch() so that both sets of code can always remain uncommented (at my age I find it hard to work with commented code shown in gray - yes I know I can change that color, but still my eyes usually gloss over comments when I am not specifically looking for them) and then all that needs to be commented out, if necessary, is one of the two lines at top.
The only problem I am having is, that I wanted to separate out this one common set of lines setting up the sheet and row count, since that shouldn’t be necessary to repeat twice, but it’s not getting called correctly inside either singleColumnSearch() or multipleColumnSearch().
If you could look at that, and generally bless or improve the remainder of the script as shown below, then I think we’re ready to lock this one down and call it a major success!
tell application "Numbers"
activate
set theTable to document 1's sheet 1's table 1
set rowCount to theTable's row count
end tell
singleColumnSearch()
--multipleColumnSearch()
tell application "Numbers"
activate
set theTable to document 1's sheet 1's table 1
set rowCount to theTable's row count
end tell
on singleColumnSearch()
set cellRange to "G1:G" & rowCount
set allColumnChanges to {{{"Messaging", "Text"}}}
searchReplace(theTable, rowCount, cellRange, allColumnChanges)
set cellRange to "H1:H" & rowCount
set allColumnChanges to {{{"in", "Incoming"}, {"out", "Outgoing"}}}
searchReplace(theTable, rowCount, cellRange, allColumnChanges)
end singleColumnSearch
on multipleColumnSearch()
-- Assuming each column has its own changes or set of changes to be made,
-- the idea here is to get a list of lists representing the columns, each column sublist
-- containing lists of text pairs for the search/replace operations.
set cellRange to "G1:H" & rowCount
set gColumnChanges to {{"Messaging", "Text"}} -- One possible change in column "G".
set hColumnChanges to {{"in", "Incoming"}, {"out", "Outgoing"}} -- Two in column "H".
set allColumnChanges to {gColumnChanges, hColumnChanges}
searchReplace(theTable, rowCount, cellRange, allColumnChanges)
end multipleColumnSearch
on searchReplace(theTable, rowCount, cellRange, changeCriteria)
script o
property cellValues : missing value
end script
set o's cellValues to selectAndGetValuesFromRange(theTable, cellRange)
-- There should be the same number of lists in changeCriteria as there are columns to handle!
set columnCount to (count changeCriteria)
repeat with r from 1 to rowCount
-- Get a list of the columnCount values from thisRow.
set rowValues to o's cellValues's items (r * columnCount - (columnCount - 1)) thru (r * columnCount)
-- Edit each value as necessary using the relevant search/replace criteria.
repeat with c from 1 to columnCount
set cellValue to rowValues's item c
if (cellValue is missing value) then
set cellValue to ""
else
set relevantCriteria to changeCriteria's item c
repeat with thisChange in relevantCriteria
set {searchText, replaceText} to thisChange
if (cellValue contains searchText) then
set cellValue to replaceText(cellValue, searchText, replaceText)
end if
end repeat
end if
set rowValues's item c to cellValue
end repeat
-- Coerce the row list to text with a tab delimiter, append a tab at the end,
-- and store the text in slot r of o's cellValues.
set o's cellValues's item r to join(rowValues, tab) & tab
end repeat
-- Coerce the stored texts to a single linefeed-delimited one and paste.
set columnText to join(o's cellValues's items 1 thru r, linefeed)
pasteIntoNumbers(columnText)
end searchReplace
on replaceText(this_text, search_string, replacement_string)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to the search_string
set the item_list to every text item of this_text
set AppleScript's text item delimiters to the replacement_string
set this_text to the item_list as text
set AppleScript's text item delimiters to astid
return this_text
end replaceText
on join(lst, delim)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set txt to lst as text
set AppleScript's text item delimiters to astid
return txt
end join
on selectAndGetValuesFromRange(theTable, cellRange)
tell application "Numbers"
set theTable's selection range to theTable's range cellRange
return theTable's selection range's cells's formatted value
end tell
end selectAndGetValuesFromRange
on pasteIntoNumbers(txt)
set the clipboard to txt
tell application "System Events"
set frontmost of application process "Numbers" to true
keystroke "v" using {shift down, option down, command down}
end tell
delay 1 -- Allow time for the paste to complete. (Adjust if/as necessary.)
end pasteIntoNumbers
Also, can I assume that any other things that need to be done to the sheet (i.e. formatting columns and cells and such) would simply go inside that main() handler either above or below the existing lines that performs he search/replace?
So, for the benefit of anyone else following this, let me just explain again what we’re doing here. Numbers does not allow search/replace on selected rows, columns, cells - it’s the entire sheet or nothing, which is frankly insane. Moreover, even if it did, that wouldn’t help solve the problem of repeating such a task through multiple sheets, which this script does both elegantly and quickly.
The main difference between this rev and all the earlier posts is, that in order to have the most flexibility in both the target range and search terms, that required digging through all the main code to customize as needed. Here, what @Nigel_Garvey has brilliantly done is incorporate all of the user-input values inside the small main() section on top, leaving the rest of the code invulnerable to newbies messing it up
And as to why the need for separate code to handle both adjoining columns and individual columns, it’s all about speed, which matters most when the sheets are very large or when there are multiple sheets being processed. So my suggestion is to always take the multiple columns route when that is feasible, and only use the separate columns code when it is not.