How could I clean this up with Keyboard Maestro

let's say I have text that looks like this:

  • Source: S38 Stato d’anima 1900. Film 1732191, Page 458 of 872 accessed 7 Jul 2024
  • Source: S38 Chiesa cattolica San Pietro Apostolo Registri ecclesiastici di Rovinaglia (Parma), 1597-1932 Publication: Name: Laboratorio di Ecologia dell'Università di Parma; Location: Pontassieve, Italy; Date: 1977;

What might I do for a Keyboard macro to remove the "Source: S38" (given that this is not a static entry, but it might be S1, S2, S3, S_infinitum) to end up with the lines reversed and a result of:

  • Chiesa cattolica San Pietro Apostolo Registri ecclesiastici di Rovinaglia (Parma), 1597-1932 Publication: Name: Laboratorio di Ecologia dell'Università di Parma; Location: Pontassieve, Italy; Date: 1977; Stato d’anima 1900. Film 1732191, Page 458 of 872 accessed 7 Jul 2024

I'm assuming highlight the text, copying into a named clipboard or similar, making some adjustments and pasting the outcome. It's the fact that I'm not searching for a specific "S" value that I don't seem to have figured out.

Thanks!

Danita

PS this is a bit overly simplified, because here we have just two lines, but I might have multiple lines where there are multiple S values and duplicate S values, but I think maybe it's easier to start with a single possibility and then move from there.

Scripting a little in the dark without seeing a literal example of several contiguous entries

(those bullets are leading plain text dashes ?)

```
You can show literal text by preceding and following with triple backticks
```

but this kind of pattern might be one approach, assuming that you capture to the local_Source variable from the clipboard, and paste with the result:

Tidied film reference pairs (rough draft).kmmacros (5.2 KB)


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

    const main = () => {
        const rgxSourceRef = /^([\- ]*Source: S\d+)(.*)$/u;

        return groupOnKey(fst)(
            lines(kmvar.local_Source)
                .flatMap(s => {
                    const m = s.match(rgxSourceRef);

                    return m
                        ? [[m[1], m[2].trim()]]
                        : [];
                })
        )
            .map(
                pair => `- ${pair[1][1][1]} ${pair[1][0][1]}`
            )
            .join("\n\n")
    };

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

    // fst :: (a, b) -> a
    const fst = tpl =>
        // First member of a pair.
        tpl[0];


    // groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
    const groupBy = eqOp =>
        // A list of lists, each containing only elements
        // equal under the given equality operator, such
        // that the concatenation of these lists is xs.
        xs => 0 < xs.length
            ? (() => {
                const [h, ...t] = xs;
                const [groups, g] = t.reduce(
                    ([gs, a], x) => eqOp(a[0])(x)
                        ? [gs, [...a, x]]
                        : [[...gs, a], [x]],
                    [[], [h]]
                );

                return [...groups, g];
            })()
            : [];


    // groupOnKey :: Eq k => (a -> k) -> [a] -> [(k, [a])]
    const groupOnKey = f =>
        // A list of (k, [a]) tuples, in which each [a]
        // contains only elements for which f returns the
        // same value, and in which k is that value.
        // The concatenation of the [a] in each tuple === xs.
        xs => 0 < xs.length
            ? groupBy(a => b => a[0] === b[0])(
                xs.map(x => [f(x), x])
            )
                .map(gp => [
                    gp[0][0],
                    gp.map(ab => ab[1])
                ])
            : [];


    // 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();
})();

You could copy to a variable (or clipboard) and then search for the regular expression Source: S\d+ and replace with nothing. (\d is any digit and + means one or more)

To reverse the order of the lines you could do a For Each line in (variable/clipboard) Prepend Line to NewVariable... which will then have the lines in reverse order. I've attached a crude attempt that may help

Reverse lines and remove S##.kmmacros (4.5 KB)

Echoing @ComplexPoint -- it isn't clear if "Source" is the first word of an entry or if they do actually start with bullets or similar. And you seem to have two entries in your source but only one in your expected output -- I'm going to assume there should be two.

Assuming it is the first word, it sounds like what you want is to

  1. Delete from the start of every line the text "Source:" and a space and an "S" and one or more numbers and a space
  2. Reverse the lines

For the first, you can delete with "Search and Replace" -- search for your pattern and replace with nothing (leave the box empty). Being ultra careful and pinning the pattern to the beginning of the line, something like

(?m)^Source:\sS\d+\s

...should work.

For reversing lines, just use the "Filter" action with "Reverse lines" selected.

Putting those together, and working on the System Clipboard so you can Copy, run the macro, Paste the transformed text:

Source cleanup.kmmacros (2.0 KB)

I'm sure these people are giving you good advice, but my Filter Utility can do it all in one action, assuming you pass the data into my macro using a variable, like this:

However for this to work you have to make a tiny change to my macro, changing this action:

to this:

(I was planning to make that change for v1.1 of my macro.)

This solution may give you a few things to handle separately. For example, I don't know what app you are using so I don't know if this will deal with the bullets properly. Also, you may end up with a leading or trailing space, but that should be easy to fix. Also, I didn't deal with the copy/paste part of your problem, partly because I don't know which app you are using.

My macro can be found here:

My macro is designed to work with plaintext, while your problem also requires dealing with an app that is probably RTF, not plaintext. But my macro can still do most of the heavy lifting in a really simple interface for you.

Sorry - I pasted the text directly, and the asterisks became a bullet. They start with "* Source: " - I've gotten some hints here that i think I can work with - I'll see if I can figure out at least how to do "one" set and then we'll go from there.

Don't forget that the * is a special character in a regex pattern, so you'll need to "escape" it to match a literal *:

(?m)^\*\sSource:\sS\d+\s

Ahh, that makes sense. Sometimes this website is too smart for its own good.