I Need Help Using Multiple Lines on the Clipboard

1.180 cm

3.009 cm

0.692 cm

0.645 cm

Please help me

I have those values stored in my clipboard.

When I paste then I get that exact result posted. I get a blank line between each value.

I would like to store the individual values into individual variables, for example, from VAR1 to VARn from the smallest to the biggest value...

Can you help me with ideas?

Thanks

Something like this should work:

Collect numbers.kmmacros (2.4 KB)

It runs this Python script to pull out the values and put them into JSON form:

#!/usr/bin/python3

import re
import sys
import json

# Read standard input and save to string
inp = sys.stdin.read()

# Collect the nonblank lines into a list
lines = re.split(r'\n+', inp.strip())

# Make a list of (number, unit) tuples
nums = []
for s in lines:
	# Find the numeric part
	match = re.match(r'[-.0-9]+', s)
	if match:
		# Break into number and unit
		numPart = match.group(0)
		unitPart = s[len(numPart):].strip()
		# Add (number, unit) tuple to the end of list
		nums.append((float(numPart), unitPart))

# Sort numerically
nums.sort()

# Convert to list of strings
values = [ f'{n} {u}' for n, u in nums ]

# Print in JSON form
print(json.dumps(values))

It then uses the Set Variables to JSON action to create a set of variables named MYVAR1, MYVAR2, etc. These variables are strings that have both the number and the unit. They're sorted by the numeric part, so "1 cm" will come before "2 mm."

The Python script tries to handle variations in the input—missing or extra blank lines, missing or extra space between the number and the unit, lines with no numbers, lines with no units—in a reasonable way, but it's certainly not unbreakable.

What is your ultimate objective?
Many times it is not that useful to store data in a bunch of numbered KM Variables.
It can be much more effective and easy to use to the KM For Each action with a Lines In collection, which can even be set to ignore blank lines.

So you could have something like this:

image

Of course, within the For Each Action, you can put whatever Actions you want to process each line.

Note that I use the KM Variable Local__Line which is a Local Variable, which is automatically deleted from the KM Variable list.

Questions?

and just in case a numbered and value-ordered sequence of KM variables really is what you need, here is a variant which uses an Execute JavaScript action:

Numbered variables from clipboard lines.kmmacros (25.3 KB)

JS Source
(() => {
    'use strict';

    ObjC.import('AppKit');

    const kmVarNameStem = 'var';

    // Numbered sequence of Keyboard Maestro variables
    // created from any floatNumber unitString lines
    // in the macOS system clipboard.

    // main :: IO ()
    const main = () =>
        either(
            // An explanatory message,
            alert('Variables from clipboard lines')
        )(
            // or a list of the KM variable names bound.
            x => x
        )(
            bindLR(
                clipTextLR()
            )(
                clip => bindLR(
                    sortedTuplesFromClipLR(clip)
                )(
                    pairs => Right(
                        numberedKMvarsFromPairs(pairs)
                    )
                )
            )
        );

    // numberedKMvarsFromPairsLR :: [(Int, String)] ->
    // IO [Keyboard Maestro Variable]
    const numberedKMvarsFromPairs = pairs => {
        const
            kme = Application('Keyboard Maestro Engine'),
            indexDigitCount = str(pairs.length).length;
        return pairs.reduce(
            (a, tuple, i) => {
                const
                    k = kmVarNameStem + str(1 + i)
                    .padStart(indexDigitCount, '0'),
                    v = unwords(first(str)(tuple));
                return (
                    kme.setvariable(k, {
                        to: v
                    }),
                    `${a}\n${k} -> ${v}`
                );
            },
            ''
        );
    };

    // sortedTuplesFromClip :: String -> 
    // Either String [(Float, String)]
    const sortedTuplesFromClipLR = clip => {
        // Either a message or a sorted list
        // of (Float, String) tuples.
        const
            pairs = lines(clip).flatMap(
                text => {
                    const ws = words(text);
                    return isNaN(ws[0]) ? (
                        []
                    ) : [
                        [parseFloat(ws[0])].concat(
                            unwords(ws.slice(1))
                        )
                    ]
                }
            );
        return 0 < pairs.length ? (
            Right(sortBy(comparing(fst))(pairs))
        ) : Left(
            'No (number, unit) pairs seen in clipboard.'
        );
    };


    // ---------------------- MACOS ----------------------

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

    // clipTextLR :: () -> Either String String
    const clipTextLR = () => (
        v => Boolean(v) && 0 < v.length ? (
            Right(v)
        ) : Left('No utf8-plain-text found in clipboard.')
    )(
        ObjC.unwrap($.NSPasteboard.generalPasteboard
            .stringForType($.NSPasteboardTypeString))
    );

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

    // https://github.com/RobTrew/prelude-jxa

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


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


    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a =>
        b => ({
            type: 'Tuple',
            '0': a,
            '1': b,
            length: 2
        });


    // bindLR (>>=) :: Either a -> 
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.Right);


    // comparing :: (a -> b) -> (a -> a -> Ordering)
    const comparing = f =>
        x => y => {
            const
                a = f(x),
                b = f(y);
            return a < b ? -1 : (a > b ? 1 : 0);
        };

    // cons :: a -> [a] -> [a]
    const cons = x =>
        // A list constructed from the item x,
        // followed by the existing list xs.
        xs => [x].concat(xs)


    // 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 => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // first :: (a -> b) -> ((a, c) -> (b, c))
    const first = f =>
        // A simple function lifted to one which applies
        // to a tuple, transforming only its first item.
        xy => {
            const tpl = Tuple(f(xy[0]))(xy[1]);
            return Array.isArray(xy) ? (
                Array.from(tpl)
            ) : tpl;
        };

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


    // last :: [a] -> a
    const last = xs =>
        // The last item of a list.
        0 < xs.length ? (
            xs.slice(-1)[0]
        ) : undefined;

    // lines :: String -> [String]
    const lines = s =>
        // A list of strings derived from a single
        // string delimited by newline and or CR.
        0 < s.length ? (
            s.split(/[\r\n]+/)
        ) : [];


    // list :: StringOrArrayLike b => b -> [a]
    const list = xs =>
        // xs itself, if it is an Array,
        // or an Array derived from xs.
        Array.isArray(xs) ? (
            xs
        ) : Array.from(xs || []);


    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );

    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
        xs => list(xs).slice()
        .sort((a, b) => f(a)(b));

    // sj :: a -> String
    function sj() {
        // Abbreviation of showJSON for quick testing.
        // Default indent size is two, which can be
        // overriden by any integer supplied as the
        // first argument of more than one.
        const args = Array.from(arguments);
        return JSON.stringify.apply(
            null,
            1 < args.length && !isNaN(args[0]) ? [
                args[1], null, args[0]
            ] : [args[0], null, 2]
        );
    }

    // str :: a -> String
    const str = x =>
        Array.isArray(x) && x.every(
            v => ('string' === typeof v) && (1 === v.length)
        ) ? (
            x.join('')
        ) : x.toString();

    // unwords :: [String] -> String
    const unwords = xs =>
        // A space-separated string derived
        // from a list of words.
        xs.join(' ');

    // words :: String -> [String]
    const words = s =>
        // List of space-delimited sub-strings.
        s.split(/\s+/);

    // MAIN --
    return main();
})();