Converting Clipboard or Variable Plain text

I found a way to solve your problem using only KM actions (I don't mind if you prefer using the Javascript solution provided above, but for personal reasons I had to find a solution that uses KM.) I can't guarantee it will work with your app because I didn't test your app's links. But here's how it works for me using hyperlinks created from web browsers placed into the system clipboard:

The trick was finding a way that produced both the hyperlink and its display name. My solution was to write the clipboard to a file specifying plaintext output, and then reading the file back in. But some forms of reading didn't work, like the KM action. My method works, but I don't really understand why. I may be able to simplify it, but this is pretty simple.

As you can see my method extracts the link using a regex. I'm not sure if this method will work for you because I can't see the details of your app's links. And my method may not work for any link whose display name or hyperlink contains a double quote. But it should work for the majority of cases. If you want to fine tune it, we can try that later.

OOPS there appears to be a filename typo. I'll fix that. It's fixed.

Let me see if I have understood ...

  • You feel that a Keyboard Maestro Execute Shell Script action is a Keyboard Maestro Action, but
  • a Keyboard Maestro Execute JavaScript for Automation action is not a Keyboard Maestro action ?

Ok, you got me. I was wrong. I lose, ergo, you win. You're the better person. (This is a humorous expression I use in real life a lot.)

On the other hand, the word "cat" seems easier to understand than:

(() => {
    "use strict";

    ObjC.import("AppKit");

    const main = () =>
        clipOfType("public.url");

    // --------------------- GENERIC ---------------------

    // clipOfType :: String -> Either String String
    const clipOfType = utiOrBundleID => {
        const
            clip = ObjC.deepUnwrap(
                $.NSString.alloc.initWithDataEncoding(
                    $.NSPasteboard.generalPasteboard
                    .dataForType(utiOrBundleID),
                    $.NSUTF8StringEncoding
                )
            );

        return 0 < clip.length ? (
            clip
        ) : (
            "No clipboard content found " + (
                `for type '${utiOrBundleID}'`
            )
        );
    };

    return main();
})();
1 Like

I agree that Apple Script is hard to read. I'm a recovering programmer and while I can this code, I couldn't have written.

That said it does the job. I almost wonder if I should refactor into a "library" like function for general use in KM. That said I've always like the rule of '3' from the Gang of Four patterns book. You need to see something in use three times before attempting to generalize.

I think that was JavaScript not AppleScript. Which proves my point. :slight_smile:

1 Like

True. I still managed to parse it. I think it was the use of NSObject, etc which my brain sees as very mac like. Caveat emptor, I last coded on Mac professionally when munger was a function.

It's JavaScript, or more technically, JavaScript for Automation, otherwise known as "JXA". It uses a call to the Objective-C library available in JXA, which is what makes it look so strange.

Yes it's a good idea to take code like this and stick it in a subroutine-type of macro.

If I want to make a piece of copied text plain text without any embedded links etc, I make it into a Variable. Variables are plain text with no other info such as styles or links.

1 Like

Using that Set Variable action on a link strips out the hyperlink rather than stripping out the link's visible text, which is the opposite of what needs to be done in this case.

If I recall, it took me four hours to come up with a solution that strips out the visible text instead of the hyperlink. It was a tough problem.

I did try a version of that early on and it didn't work. Since I can't easily tell the state of the clipboard from visual inspection I couldn't tell the why.

I almost wish that @peternlewis would give us an assertion facility for KM. Then I could assert that the clipboard was in a known state, the assertion would fail and I would iterate.

(Can you tell I spent 20+ yrs writing code and was one of the earliest adopters of both JUnit and NUnit :wink:

1 Like

I'm not sure how an assert action would help you here. In the languages that I know, Assert's simply abort the macro if the condition isn't true.

But in fact, there is an assert action in KM with dozens of different tests you can perform on the clipboard... like this one...

1 Like

Well who knew? Not me.

Use - I would Assert that the Clipboard wasn't plain text and display a message to that effect. I allow me to test my assumptions more cleanly.

I played for 10 secs. The missing detail - there is no builtin way to test whether on not the clipboard is plain text.

You might find the answer to your question here:

You will need to test, but this seems to do it:

Of course, my code will actually solve the problem, not just detect the type of the clipboard.

1 Like

Ah. I had missunderstood. I thought he wanted to turn the copied text into plain text and strip out a hyperlink. In my defence that’s what he seemed to be asking :slightly_smiling_face:

You're a good person, Zab. So I'm always afraid to argue with you. I don't want to earn your wrath.

So, the goal here is just to get the hyperlink with nothing else?

That's what I think he wanted. My solution above achieved it, but it was messy. You have a cleaner solution?

When you copy a selection in a macOS application, the pasteboard typically contains material of various different types.

If I copy from Chrome for example,

  • There is public.html content,
  • but also, separately and simultaneously public.utf8-plain-text

if I copy some text in OmniGraffle, then the types of content in the pasteboard may include, simultaneously, and inter alia:

  • public.rtf
  • public.utf8-plain-text
  • public.utf16-external-plain-text

i.e. the question is not what the type of the clipboard 'is', but how many different pasteboard items it contains, and what the type of each of them is.

Through osascript (in AppleScript or JS idioms) you can get a listing with incantations like:

$.NSPasteboard.generalPasteboard.pasteboardItems[0].types


See, for example:

Clipboard Viewer (all textually representable data as JSON).kmmacros (4.9 KB)


and more generally:

NSPasteboard | Apple Developer Documentation


and

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    const main = () =>
        clipTextLR();

    // --------------------- GENERIC ---------------------

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => {
        // Either a message, (if no clip text is found),
        // or the string contents of the clipboard.
        const
            v = ObjC.unwrap(
                $.NSPasteboard.generalPasteboard
                .stringForType($.NSPasteboardTypeString)
            );

        return Boolean(v) && 0 < v.length ? (
            Right(v)
        ) : Left("No utf8-plain-text found in clipboard.");
    };

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });

    return main();
})();

finally:

NSPasteboardType | Apple Developer Documentation

1 Like

Wow. I feel like I've just walked in to some famous building of great antiquity, like the Taj Mahal, or Westminster Abbey, or St Basil's Cathedral, or St Peter's Basilica, or Petra. "I've never seen anything like this before."

Wow. I last coded against clipboards, ~20 yrs ago on Windows and Unix. Thanks for reminding me of the pain.

I of course blame Obsidian for not being willing to down convert from a richer source :-).

Time for a weekend and wine.
- Mark