Rough sketch (you could refine the intermediate HTML markup for fonts, alignments etc), and you might prefer textutil for the HTML -> RTF conversion, but here in a JavaScript for Automation version:
Expand disclosure triangle to view JS source
(() => {
"use strict";
// Rob Trew @2023
// Draft 00.01
ObjC.import("AppKit");
const columnDelimiter = ":";
// Column delimited lines copied as RTF table
// (via HTML markup)
const main = () => {
const kmv = kmValue(kmInstance());
return either(
alert("Delimited rows copied as RTF table")
)(
copyTypedString(true)("public.rtf")
)(
rtfFromHTML(
htmlTable(
lines(
kmv("local_ColonDelimitedLines")
)
.map(x => x.split(columnDelimiter))
)
)
);
};
// ---------------- KEYBOARD MAESTRO -----------------
// kmInstance :: () -> IO String
const kmInstance = () =>
ObjC.unwrap(
$.NSProcessInfo.processInfo.environment
.objectForKey("KMINSTANCE")
) || "";
// kmValue :: KM Instance -> String -> IO String
const kmValue = instance =>
k => Application("Keyboard Maestro Engine")
.getvariable(k, {instance});
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// copyTypedString :: Bool -> String -> String -> IO ()
const copyTypedString = blnClear =>
// public.html, public.rtf, public.utf8-plain-text
pbType => s => {
const pb = $.NSPasteboard.generalPasteboard;
return (
blnClear && pb.clearContents,
pb.setStringForType(
$(s),
$(pbType)
)
);
};
// ---------------------- HTML -----------------------
// htmlTable :: [[String]] -> HTML String
const htmlTable = rows => {
const
thead = "<thead></thead>",
htmlrows = rows.map(
row => `<tr>${row.map(x => `<td>${x}<td>`)
.join("")}</tr>`
)
.join("\n"),
tbody = `<tbody>\n${htmlrows}\n</tbody>`;
return `<table>\n${thead}\n${tbody}\n</table>`;
};
// rtfFromHTML :: String -> Either String String
const rtfFromHTML = strHTML => {
const
as = $.NSAttributedString.alloc
.initWithHTMLDocumentAttributes($(strHTML)
.dataUsingEncoding($.NSUTF8StringEncoding),
0
);
return bindLR(
"function" !== typeof as
.dataFromRangeDocumentAttributesError ? (
Left("String could not be parsed as HTML")
) : Right(as)
)(
// Function bound if Right value obtained above:
htmlAS => {
const
error = $(),
rtfData = htmlAS
.dataFromRangeDocumentAttributesError({
"location": 0,
"length": htmlAS.length
}, {
DocumentType: "NSRTF"
},
error
);
return Boolean(
ObjC.unwrap(rtfData) && !error.code
) ? Right(
ObjC.unwrap($.NSString.alloc
.initWithDataEncoding(
rtfData,
$.NSUTF8StringEncoding
))
) : Left(ObjC.unwrap(
error.localizedDescription
));
}
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single string
// which is delimited by \n or by \r\n or \r.
0 < s.length
? s.split(/\r\n|\n|\r/u)
: [];
return main();
})();
It should just copy to clipboard if that's what you mean.
No dependencies, but I would try the JS code on its own,
(i.e. build your own macro) with a variable name of your own at the point where I have written:
kmv("ColonDelimitedLines")
whereas I notice that the variable in the macro is local_ColonDelimitedLines
mea culpa
I'll correct the macro and source listing above in a minute.
Done.
Working here with macOS 13.5.2
The RTF source should be writable to an .rtf file yes. I'll sketch an approach later.
Here's a copy of the source which writes to a local file as well as copying to a public.rtf pasteboard item (again, adjust line 30 to match a KM variable name of your own) e.g.
kmv("ColonDelimitedLines")
Expand disclosure triangle to view JS source
(() => {
"use strict";
// Rob Trew @2023
// Draft 00.02
ObjC.import("AppKit");
const columnDelimiter = ":";
// Column delimited lines copied as RTF table
// (via HTML markup)
// Also written to local file in this copy.
const main = () => {
const kmv = kmValue(kmInstance());
return either(
alert("Delimited rows copied as RTF table")
)(
rtf => (
copyTypedString(true)("public.rtf")(rtf),
writeFile("~/Desktop/tabular.rtf")(rtf)
)
)(
rtfFromHTML(
htmlTable(
lines(
kmv("ColonDelimitedLines")
)
.map(x => x.split(columnDelimiter))
)
)
);
};
// ---------------- KEYBOARD MAESTRO -----------------
// kmInstance :: () -> IO String
const kmInstance = () =>
ObjC.unwrap(
$.NSProcessInfo.processInfo.environment
.objectForKey("KMINSTANCE")
) || "";
// kmValue :: KM Instance -> String -> IO String
const kmValue = instance =>
k => Application("Keyboard Maestro Engine")
.getvariable(k, {instance});
// ----------------------- JXA -----------------------
// alert :: String => String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// copyTypedString :: Bool -> String -> String -> IO ()
const copyTypedString = blnClear =>
// public.html, public.rtf, public.utf8-plain-text
pbType => s => {
const pb = $.NSPasteboard.generalPasteboard;
return (
blnClear && pb.clearContents,
pb.setStringForType(
$(s),
$(pbType)
)
);
};
// writeFile :: FilePath -> String -> IO ()
const writeFile = fp => s =>
$.NSString.alloc.initWithUTF8String(s)
.writeToFileAtomicallyEncodingError(
$(fp)
.stringByStandardizingPath, false,
$.NSUTF8StringEncoding, null
);
// ---------------------- HTML -----------------------
// htmlTable :: [[String]] -> HTML String
const htmlTable = rows => {
const
thead = "<thead></thead>",
htmlrows = rows.map(
row => `<tr>${row.map(x => `<td>${x}<td>`)
.join("")}</tr>`
)
.join("\n"),
tbody = `<tbody>\n${htmlrows}\n</tbody>`;
return `<table>\n${thead}\n${tbody}\n</table>`;
};
// rtfFromHTML :: String -> Either String String
const rtfFromHTML = strHTML => {
const
as = $.NSAttributedString.alloc
.initWithHTMLDocumentAttributes($(strHTML)
.dataUsingEncoding($.NSUTF8StringEncoding),
0
);
return bindLR(
"function" !== typeof as
.dataFromRangeDocumentAttributesError ? (
Left("String could not be parsed as HTML")
) : Right(as)
)(
// Function bound if Right value obtained above:
htmlAS => {
const
error = $(),
rtfData = htmlAS
.dataFromRangeDocumentAttributesError({
"location": 0,
"length": htmlAS.length
}, {
DocumentType: "NSRTF"
},
error
);
return Boolean(
ObjC.unwrap(rtfData) && !error.code
) ? Right(
ObjC.unwrap($.NSString.alloc
.initWithDataEncoding(
rtfData,
$.NSUTF8StringEncoding
))
) : Left(ObjC.unwrap(
error.localizedDescription
));
}
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single string
// which is delimited by \n or by \r\n or \r.
0 < s.length
? s.split(/\r\n|\n|\r/u)
: [];
return main();
})();