Deleting a range moves cells up or to the left, depending on what the argument specifies.
The following example deletes duplicate rows based on identical values in the first column. Note that the loop goes from largest row to smallest "backwards" via
Step -1
because Microsoft Excel
renumbers the rows after a deletion. This is what AppleScript always does in its own indexing, so you would have to do that anyway (repeat with i from lastRowNum to 1 by -1). In Visual Basic for Applications (VBA):
Dim i As Long With ActiveSheet For i = .Cells(.Rows.Count, 1).End(xlUp).Row To 2 Step -1 If .Cells(i, 1).Value = .Cells(i - 1, 1).Value Then _ .Cells(i, 1).EntireRow.Delete Next i End With
In AppleScript:
tell application "Microsoft Excel" tell active sheet set lastRowNum to first row index of ¬ (get end (cell 1 of row (count rows)) ¬ direction toward the top) repeat with i from lastRowNum to 2 by -1 if value of (cell 1 of row i) = ¬ value of (cell 1 of row (i - 1)) then delete entire row of (cell 1 of row i) end if end repeat end tell end tell
In AppleScript, putting the evaluation of the row number right into the
repeat
loop statement, as VBA does with its shorter terms and dot syntax, is too complex. It's better to work it out as a variable. As discussed in the
Known issues in Excel 2004 topic, it is essential to use the application terms directly without variables for a row, but it always works to set a variable to a range or, as shown here, to a number (or string, date, or other basic type).
With long trails of dots in VBA, it is natural when translating into AppleScript to expect to start at the end and work backwards from there, except that you actually need to start off with the "front" VBA object in order to know where to look for the properties that follow. In VBA, a method name might occur somewhere along the way in the same dot syntax and parent-child relation as for a property. In AppleScript, however, the equivalent command does not use the same
of
syntax as a property or element, and has to be sought in the commands section of the appropriate dictionary suite.
A property in VBA might be an AppleScript element, or property of an element, or command. So it can take a bit of time to find everything and work it all out.
The
Cells(r, c)
syntax translates to the following in AppleScript, and the result is a range.
cell c of row r
So you look in range class in the Table Suite for something analogous to the End property — it might be end of range or something like that. But there's nothing at all like that to be seen in range class.
So you look now in the section about commands in the Table Suite for something that might get the end of a range. There's nothing listed under
end of...
there. So check the get neighborhood — always a good idea. When AppleScript needs a proprietary command because a property won't work, the command term often starts with get.
And there it is — get end. It takes a range as direct object or parameter ("direct" because no specific parameter keyword is needed) and its direction parameter takes an enumeration that is just what you need, specifying toward the top for VBA's xlTop. In this case it really means "last one, or end, starting at the top", that is, oriented from the top's point of view. Of course, you're actually looking for the bottom row.
Although you can't get rows or every row of a range or sheet, you can
get count rows
(or
count every row). Finally the Row property of the Range method is first row index property in AppleScript — a descriptive term that returns an integer, which is just what you need. (As expected, AppleScript does not call this property
row; it would conflict with the class row.)
So now you have everything you need to construct the statement that should return the row number of a cell.
set lastRowNum to first row index of ¬ (get end (cell 1 of row (count rows)) direction toward the top)
Check whether AppleScript lets you use the get end command on the sheet itself (the target of the
tell
block) without producing
65535
as the last row of the sheet instead of the last used row. Also check whether you are required to explicitly target used range of the sheet instead. You will find that AppleScript allows you to take the same shortcut (or coercion) as Sheet.End does in VBA. You get the last used row number, not
65535.
The rest of the script is easy, using
cell c of row i
a few more times, and getting the value property of each range (cell). The
delete entire row
statement works just as it should, as long as you remember to do your
repeat
loop backwards
by -1, which is the equivalent of VBA's
Step -1.
A more efficient way to do this is to collect all of the duplicates in a range variable and delete them all at the same time. Note that the Union method in VBA requires that both Range objects are set, and the same is true in AppleScript with the union command.
Dim rCell As Range
Dim rDelete As Range
With ActiveSheet
For Each rCell In .Range("A2:A" & _
.Range("A" & .Rows.Count).End(xlUp).Row)
If rCell.Value = rCell.Offset(-1, 0).Value Then
If rDelete Is Nothing Then
Set rDelete = rCell
Else
Set rDelete = Application.Union(rDelete, rCell)
End If
End If
Next rCell
If Not rDelete Is Nothing Then _
rDelete.EntireRow.Delete
End WithHere's the AppleScript version:
tell application "Microsoft Excel"
tell active sheet
set lastRowNum to first row index of ¬
(get end (cell 1 of row (count rows)) ¬
direction toward the top)
set rDelete to missing value
repeat with i from 1 to (count cells ¬
of range ("A2:A" & lastRowNum))
--that's column A from 2nd cell to bottom used cell
set rCell to (cell i of range ("A2:A" & lastRowNum))
if value of rCell = value of ¬
(get offset rCell row offset -1) then
if rDelete = missing value then
set rDelete to rCell
else
set rDelete to union ¬
range1 rDelete range2 rCell
end if
end if
end repeat
if rDelete missing value then delete entire row of rDelete
end tell
end tellOnce again you set a variable to the last row number to make things more manageable in the
repeat
statement. In AppleScript, a variable that is not defined is not equal to
null
(our rarely used version of
Nothing). Instead, it causes an error if called. Normally you trap that error with a
try/on
error
block, but in this case it also puts the union statement in the same
try
block and so perhaps catches a different error (leading to a never-ending loop that keeps setting
rDelete
to the first cell). Or, it requires some complex error management, plus inverts the order of things to come, making comparisons more difficult.
So it makes good sense to initialize
rDelete
to a value it couldn't possibly have otherwise. You could set it to anything you like that's not an Excel
range, and common initializations are to
""
or
0. But you might as well use AppleScript's handy expression for something undefined but which does not error —
missing value. (You see that a lot if either you or Excel
makes a mistake in scripting.) It's not exactly
null, but it's just what you want here.
In the same that way you cannot get
(every
cell of someRange)
or set a variable to it, but you can
count cells of someRange (!), you have to construct the
repeat
loop to avoid the
repeat with rCell in (cells of someRange)
syntax. This also avoids setting
rCell
to
item i of (cells of
someRange),
someRange
in this case being
range ("A2:A" &
lastRowNum)
throughout.
Instead you must set it up so you refer to the following:
cell i of someRange
The VBA Offset method is reproduced by get offset. You have to spell out the parameter names row offset and column offset, but the default for each is 0, so you can omit column offset.
The union command, which is located in the Microsoft Excel Suite instead of the Table Suite, though it acts only on ranges, uses parameters range1, range2, etc. Also,
delete entire row
works on a non-contiguous range.
However, there is yet another way you could do this in AppleScript, not available to VBA, since the delete command can act all at once on a list of ranges.
tell application "Microsoft Excel"
tell active sheet
set lastRowNum to first row index of ¬
(get end (cell 1 of row (count rows)) ¬
direction toward the top)
set rDelete to {}
repeat with i from 1 to (count cells ¬
of range ("A2:A" & lastRowNum))
--that's column A from 2nd cell
--to bottom used cell
set rCell to (cell i ¬
of range ("A2:A" & lastRowNum))
if value of rCell = value of ¬
(get offset rCell row offset -1) then
set end of rDelete to (entire row of rCell)
end if
end repeat
if rDelete {} then delete rDelete
end tell
end tellIsn't that simple in comparison? You initialize
rDelete
to an empty list
{}. If you find a duplicate cell, you set the end of the list
rDelete
to the entire row of that cell without having to check every time to see if the list is empty or not. Then at the end you delete the list and that's that.
Accustomed as you are to VBA, you may prefer to mirror it exactly as you start converting your macros, but in time as you get accustomed to AppleScript you will, and should, find more native and natural ways to do things in your new language.


