If you copy some tab-delimited lines, for example from Excel, and want to use a script action in KM to transpose rows ⇄ columns in the clipboard
alpha beta gamma
1 4 7
2 5 8
3 6 9
⇄
alpha 1 2 3
beta 4 5 6
gamma 7 8 9
Then you could write the script in ES5 JS (from Yosemite to Sierra and onwards)
or in ES6 JS (from Sierra onwards)
on in AS (from some time ago).
Here are drafts of what each version might look like:
JS ES5
(function () {
'use strict';
// transpose :: [[a]] -> [[a]]
function transpose(rows) {
return rows[0].map(function (_, iCol) {
return rows.map(function (row) {
return row[iCol];
});
});
}
// stringMatrix :: String -> Maybe Regex -> Maybe Regex -> [[String]]
function stringMatrix(s, rgxRow, rgxCol) {
return s
.split(rgxRow || /[\n\r]+/)
.map(function (row) {
return row.split(rgxCol || /\t/);
});
}
// matrixString :: [[String]] -> String
function matrixString(lstRows) {
return lstRows
.map(function (row) {
return row.join('\t');
}).join('\n');
}
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a),
strClip = sa.theClipboard(),
strTransPosed = typeof strClip === "string" ? (
matrixString(transpose(stringMatrix(strClip)))
) : '';
return (
strTransPosed.length && sa.setTheClipboardTo(
strTransPosed
),
strTransPosed
);
})();
ES6 JS
(() => {
'use strict';
// transpose :: [[a]] -> [[a]]
let transpose = rows =>
rows[0].map((_, iCol) =>
rows.map(row => row[iCol])
),
// stringMatrix :: String ->
// Maybe Regex -> Maybe Regex -> [[String]]
stringMatrix = (s, rgxRow, rgxCol) =>
s.split(rgxRow || /[\n\r]+/)
.map(row => row.split(rgxCol || /\t/)),
// matrixString :: [[String]] -> String
matrixString = lstRows =>
lstRows.map(row => row.join('\t'))
.join('\n');
let a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a),
strClip = sa.theClipboard(),
strTransPosed = typeof strClip === "string" ? (
matrixString(transpose(stringMatrix(strClip)))
) : '';
return (
strTransPosed.length && sa.setTheClipboardTo(
strTransPosed
),
strTransPosed
);
})();
AS
-- TOGGLE ROWS <-> COLUMNS
on run
set strClip to (the clipboard as string)
if length of strClip > 0 then
set strTransposed to matrixString(transpose(stringMatrix(strClip)))
set the clipboard to strTransposed
strTransposed
else
missing value
end if
end run
-- Tab-delimited lines to list of lists of strings
-- stringMatrix :: String -> [[String]]
on stringMatrix(s)
script cells
on lambda(strLine)
splitOn(tab, strLine)
end lambda
end script
if s contains linefeed then
set strDelim to linefeed
else
set strDelim to return
end if
map(cells, splitOn(strDelim, s))
end stringMatrix
-- Lists of lists of strings to lines of text
-- matrixString :: [[String]] -> String
on matrixString(cellRows)
script rowString
on lambda(cells)
intercalate(tab, cells)
end lambda
end script
intercalate(linefeed, map(rowString, cellRows))
end matrixString
-- GENERIC LIBRARY FUNCTIONS
-- Rows <-> columns
-- transpose :: [[a]] -> [[a]]
on transpose(rows)
-- column :: a -> [a]
script column
-- Just the index of each top row item
on lambda(_, n)
-- nthCell :: [a] -> a
script nthCell
on lambda(row)
item n of row
end lambda
end script
-- Column n consists of
-- cell n of each row
map(nthCell, rows)
end lambda
end script
-- A column from each item of the top row
map(column, item 1 of rows)
end transpose
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
set mf to mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to mf's lambda(item i of xs, i, xs)
end repeat
return lst
end map
-- splitOn :: Text -> Text -> [Text]
on splitOn(strDelim, strMain)
set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
set lstParts to text items of strMain
set my text item delimiters to dlm
return lstParts
end splitOn
-- intercalate :: Text -> [Text] -> Text
on intercalate(strText, lstText)
set {dlm, my text item delimiters} to {my text item delimiters, strText}
set strJoined to lstText as text
set my text item delimiters to dlm
return strJoined
end intercalate
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: Handler -> Script
on mReturn(f)
if class of f is script then
f
else
script
property lambda : f
end script
end if
end mReturn