Faster Search/Replace In Numbers Columns

 

tell application "Numbers" to tell table 1 of sheet 1 of document 1
	
	set piece to 1000
	set rowCount to row count
	set C to 0
	
	repeat
		try
			set R to C + 1
			set C to C + piece
			set RC to "H" & R & ":H" & C -- the next range
			set value of cells of range RC whose value contains "in" to "Incomming"
			set value of cells of range RC whose value contains "out" to "Outgoing"
			if C = rowCount then exit repeat
		on error
			set RC to "H" & R & ":H" & rowCount -- the rest range
			set value of cells of range RC whose value contains "in" to "Incomming"
			set value of cells of range RC whose value contains "out" to "Outgoing"
			exit repeat
		end try
	end repeat
	
end tell

 

This works awesome - turns out it’s even faster than before for some reason. Can you explain why you have the “on error” section of the code?

The script processes per 1000 rows. Let’s say you have 6200 lines. Then you should have for the last piece: 6001-6200.

The normal part of the TRY-block gives 6001-7000 for the 7th piece. Since there is no such range (>6200), an error is thrown. This error is caught by the ON ERROR part of the TRY block and fixes 6001-7000 to 6001-6200.

Brilliant! Thanks so much for your invaluable help!

If you have a moment to look at my other two posts that I need help with, that would be great!

https://www.macscripter.net/t/format-telephone-number-in-numbers/74769/2

https://www.macscripter.net/t/using-applescript-to-set-alternating-row-color/74771

Hi.

This is quite fast in with Numbers 10.1 in Mojave:

on main()
	script o
		property allValues : missing value
	end script
	
	tell application "Numbers"
		activate
		tell table 1 of sheet 1 of document 1
			set rowCount to row count
			-- Select the whole of column H and get its values.
			set selection range to range ("H1:H" & rowCount)
			set o's allValues to value of cells of selection range
		end tell
	end tell
	
	-- Edit the values in-script.
	repeat with r from 1 to rowCount
		set this to o's allValues's item r
		if (this contains "in") then
			set o's allValues's item r to "Incoming"
		else if (this contains "out") then
			set o's allValues's item r to "Outgoing"
		else if (this is missing value) then
			set o's allValues's item r to ""
		end if
	end repeat
	
	-- Coerce the list to text with linefeed delimiters and send it to the clipboard.
	set the clipboard to join(o's allValues, linefeed)
	
	-- Paste the result into the selected Numbers column.
	tell application "System Events"
		set frontmost of application process "Numbers" to true -- Probably overkill.
		keystroke "v" using {shift down, option down, command down}
	end tell
end main

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

main()

Not sure how to incorporate that into a working script for the open document. As it is here, it does not run. Thanks.

Well. It won’t do anything if you leave out the last line, of course. :slight_smile: Apart from that, the assumptions are that you want to change values in column “H” of table 1 of sheet 1 of document 1 (as in KniazidisR’s scripts, which appear to work for you), that Numbers scripting is broadly similar on both our systems, and that GUI scripting is enabled on yours.

I don’t think GUI is enabled on mine, where do I do that on my mac?

So I circled back here to try your script on the search/replace, and I now have GUI enabled but when the try to run it, nothing happens. I press the arrow and there’s no error or anything, but it doesn’t say the script is running or anything. What could I be doing wrong?

The version of it you quoted earlier has the last line missing. It won’t do anything without that. If you click the “Open in Script Editor” under what I posted, you should get the whole thing in Script Editor (after an annoying couple of “Do you want to allow this?” dialogs).

I have this entire thing in the scripter window and it won’t execute. The arrow clicks but it does nothing, as if it’s immediately aborting?

on main()
	script o
		property allValues : missing value
	end script
	
	tell application "Numbers"
		activate
		tell table 1 of sheet 1 of document 1
			set rowCount to row count
			-- Select the whole of column H and get its values.
			set selection range to range ("H1:H" & rowCount)
			set o's allValues to value of cells of selection range
		end tell
	end tell
	
	-- Edit the values in-script.
	repeat with r from 1 to rowCount
		set this to o's allValues's item r
		if (this contains "in") then
			set o's allValues's item r to "Incoming"
		else if (this contains "out") then
			set o's allValues's item r to "Outgoing"
		else if (this is missing value) then
			set o's allValues's item r to ""
		end if
	end repeat
	
	-- Coerce the list to text with linefeed delimiters and send it to the clipboard.
	set the clipboard to join(o's allValues, linefeed)
	
	-- Paste the result into the selected Numbers column.
	tell application "System Events"
		set frontmost of application process "Numbers" to true -- Probably overkill.
		keystroke "v" using {shift down, option down, command down}
	end tell
end main

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

Also, I see that you do address the “missing value” here but can you kindly add what’s needed to the other script for the telephone formatting so that’s also covered?

I also just checked the log to see if I missed anything and it’s completely blank.

That’s not the entire thing. Type
main()
beneath what you’ve got and try again.

You are so right, I missed that and apologize. it works amazing!!! And all blank cells stay blank.

Now if I need to add a second column search (Column G, search for “Messaging” and replace with “Text”) do I need to repeat the entire thing with those changes, or can it be put into the same script?

So I worked on your script to add the second search/replace column and came up with this, which works. Wondering if you see any places where it could be cleaned up a bit?

on main()
	script o
		property allValues : missing value
	end script
	
	-- Copy this entire section to duplicate the search in another column --
	
	tell application "Numbers"
		activate
		tell table 1 of sheet 1 of document 1
			set rowCount to row count
			-- Select the whole of column G and get its values.
			set selection range to range ("G1:G" & rowCount)
			set o's allValues to value of cells of selection range
		end tell
	end tell
	
	-- Edit the values in-script.
	repeat with r from 1 to rowCount
		set this to o's allValues's item r
		if (this contains "Messaging") then
			set o's allValues's item r to "Text"
		else if (this is missing value) then
			set o's allValues's item r to ""
		end if
	end repeat
	
	-- Coerce the list to text with linefeed delimiters and send it to the clipboard.
	set the clipboard to join(o's allValues, linefeed)
	
	-- Paste the result into the selected Numbers column.
	tell application "System Events"
		set frontmost of application process "Numbers" to true -- Probably overkill.
		keystroke "v" using {shift down, option down, command down}
	end tell
	
	---------------------------------------------------------------------------
	
	tell application "Numbers"
		activate
		tell table 1 of sheet 1 of document 1
			set rowCount to row count
			-- Select the whole of column H and get its values.
			set selection range to range ("H1:H" & rowCount)
			set o's allValues to value of cells of selection range
		end tell
	end tell
	
	-- Edit the values in-script.
	repeat with r from 1 to rowCount
		set this to o's allValues's item r
		if (this contains "in") then
			set o's allValues's item r to "Incoming"
		else if (this contains "out") then
			set o's allValues's item r to "Outgoing"
		else if (this is missing value) then
			set o's allValues's item r to ""
		end if
	end repeat
	
	-- Coerce the list to text with linefeed delimiters and send it to the clipboard.
	set the clipboard to join(o's allValues, linefeed)
	
	-- Paste the result into the selected Numbers column.
	tell application "System Events"
		set frontmost of application process "Numbers" to true -- Probably overkill.
		keystroke "v" using {shift down, option down, command down}
	end tell
	
	---------------------------------------------------------------------------
	
end main

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

main()

Well. One approach would be to have the selection range cover both columns, since they’re adjacent. Within this approach, there are three possible ways to fetch and handle the cell contents.

  1. Just get the values of the selection range’s cells. This would give a flat list of the values, every two values being from the same row and each alternate one being from the same column.
  2. Get the values the selection range’s columns’ cells. This would give a list containing two sublists, each sublist containing the values from one of the columns.
  3. Get the values of the selection range’s rows’ cells. This would give a list of rowCount sublists, each sublist containing the values from one of the rows — although they’d be all the values from that row, not just those in the selected part.

Options 1 and 3 seem to be easiest from the point of view of massaging the edited data into a form where they can be pasted back into the document in one go. They’d also be quite easy to expand if more columns needed to be included. Option 1 seems to be the simplest and fastest in the present case:

on main()
	script o
		property allValues : missing value
	end script
	
	tell application "Numbers"
		activate
		tell table 1 of sheet 1 of document 1
			set rowCount to row count
			-- Select the whole of columns G and H and get the cell values.
			-- These are returned as a flat list of the values from
			-- G1, H1, G2, H2, G3, H3, … etc.
			set selection range to range ("G1:H" & rowCount)
			set o's allValues to value of cells of selection range
		end tell
	end tell
	
	-- Edit the values in-script.
	repeat with r from 1 to (rowCount * 2) by 2
		set gVal to o's allValues's item r
		if (gVal contains "Messaging") then
			set gVal to "Text"
		else if (gVal is missing value) then
			set gVal to ""
		end if
		set o's allValues's item r to gVal & tab -- Append a tab to the G value.
		set hVal to o's allValues's item (r + 1)
		if (hVal contains "in") then
			set hVal to "Incoming"
		else if (hVal contains "out") then
			set hVal to "Outgoing"
		else if (hVal is missing value) then
			set hVal to ""
		end if
		set o's allValues's item (r + 1) to hVal & linefeed -- Append a linefeed to the H value.
	end repeat
	
	-- Coerce the list to text with "" delimiters and send it to the clipboard.
	set the clipboard to join(o's allValues, "")
	
	-- Paste the result into the selected Numbers columns.
	tell application "System Events"
		set frontmost of application process "Numbers" to true -- Probably overkill.
		keystroke "v" using {shift down, option down, command down}
	end tell
end main

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

main()

Okay, that was unbelievably fast. To give you some context, doing this process within Applescript itself, for 6500 rows, took 2 min. 50 sec. What you just gave me took… are you ready for this… 3 SECONDS! I am just blown away. Even running both routines separately (what you modified) took 8 seconds, so this is obviously just blazing. The question is, for future use would this always require adjacent columns to work, or could you somehow modify it to work on any identified columns in the script?