Add same value to all numbers in a variable

I am going to receive an index for a book I need to typeset in InDesign.
My customer has already created the index. But it is based on the page numbers of Word.
I know that all the pagenumbers need to +2 higher.

This sounds like a job for Keyboard Maestro.
But I am not sure how to start.

Below is an example of the some of the index.
The numbers always comes after : and is separated with ,[space]

Hope somebody has some points on how to do this?

Index:

Opgivelse, frygt for: 14, 246, 252
Misbrug: 45, 50, 57, 59, 114, 217, 282, 296, 315
Acceptance: 11, 18, 34, 97, 216, 266, 334, 358
Ansvarlighed: 29, 78, 83, 128
Handlinger: 9, 36, 49, 78, 97, 104, 116, 124, 156, 167, 192, 287, 289, 314, 326, 341
Al-Anon som familie: 1, 85, 105, 155, 327
Alateen: 21, 150, 191, 210, 219, 281, 322
Alkoholikere, attraktion til: 313
Alkoholisme, en sygdom: 3, 6, 12, 26, 67, 121, 237, 274, 339, 344, 347
Ændringer: 49, 78, 120, 259, 296, 333
Anonymitet: 220, 254, 356
Vrede: 26, 57, 62, 68, 83, 155, 247, 257, 319, 334
Bede om hjælp: 189, 254, 264, 321, 329, 362
Myndighed: 29, 77, 133, 208, 217, 300
Bevidsthed: 97, 238, 347

Using an execute JS action:

Adjust all index pages.kmmacros (20.8 KB)

index2

JS Source

(() => {
    'use strict';

    // main :: IO ()
    const main = () => {
        const
            kme = Application('Keyboard Maestro Engine'),
            intDelta = parseInt(
                kme.getvariable('pagesDelta'),
                10
            ),
            strIndex = kme.getvariable('srcIndex');

        return unlines(map(
            ln => {
                const parts = splitOn(': ', ln);
                return 1 < length(parts) ? (
                    parts[0] + ': ' + map(
                        x => isNaN(x) ? (
                            x
                        ) : (intDelta + parseInt(x)),
                        splitOn(', ', parts[1])
                    ).join(', ')
                ) : ln;
            },
            lines(strIndex)
        ));
    };

    // GENERIC FUNCTIONS ----------------------------------------------------

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

    // length :: [a] -> Int
    const length = xs => xs.length;

    // lines :: String -> [String]
    const lines = s => s.split(/[\r\n]/);

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // splitOn :: String -> String -> [String]
    const splitOn = (needle, haystack) =>
        haystack.split(needle);

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

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

1 Like

Here's how I'd handle it with all "native" KM actions:

Add 2 to All Page Numbers.kmmacros (3.1 KB)
image

Results

Opgivelse, frygt for: 16, 248, 254
Misbrug: 47, 52, 59, 61, 116, 219, 284, 298, 317
Acceptance: 13, 20, 36, 99, 218, 268, 336, 360
Ansvarlighed: 31, 80, 85, 130
Handlinger: 11, 38, 51, 80, 99, 106, 118, 126, 158, 169, 194, 289, 291, 316, 328, 343
Al-Anon som familie: 3, 87, 107, 157, 329
Alateen: 23, 152, 193, 212, 221, 283, 324
Alkoholikere, attraktion til: 315
Alkoholisme, en sygdom: 5, 8, 14, 28, 69, 123, 239, 276, 341, 346, 349
Ændringer: 51, 80, 122, 261, 298, 335
Anonymitet: 222, 256, 358
Vrede: 28, 59, 64, 70, 85, 157, 249, 259, 321, 336
Bede om hjælp: 191, 256, 266, 323, 331, 364
Myndighed: 31, 79, 135, 210, 219, 302
Bevidsthed: 99, 240, 349

The key is this search regex, which finds every number after a colon or a comma, followed by a space:

(?<=[:,]\s)(\d+)

and this replacement, which takes the number captured by the above regex with the CALCULATE() function (in turn enabled in the replacement field via the %Calculate% token) and adds 2:

%Calculate%CALCULATE($1+2)%

6 Likes

I did not occur to me that the replace field would take calculations. Genius.

2 Likes

Slick.

1 Like

@gglick, great use of RegEx Lookbehind, which is NOT included in the match, so that the only thing matched are the digits.

And great use of the Calculate token. I don't think I've ever used it in a Replace field.

4 Likes

Thanks for the JS solution. I will use @gglick solution, since it is a bit more accessible for me to maybe change for another structure another time.

1 Like

Nice.

In this particular case the native Keyboard Maestro solution is pretty much as terse as a coded solution.

I think Peter slipped that tidbit of syntactic sugar into Keyboard Maestro in v7.x.

In a production macro I'd make a first pass to ensure proper spacing in the index.

And I think I'd change the regular expression a trifle:

From:

(?<=[:,]\s)(\d+)

To:

(?<=[:,]\h)(\d+)

To make it crystal clear we're dealing with horizontal whitespace.

Here's how I'd do this in a BBEdit Text Filter:

#!/usr/bin/env perl -nsw
use utf8;
s!\b(\d+)\b!$1 + 2!eg;
print;

I'm using word boundaries instead of Gabe's look-behind pattern, but his method is probably a little more robust (particularly if that first pass to ensure proper spacing is made).

The ‘e’ switch at the end of the pattern stands for execute on the replace side of the pattern.

Here's how I'd use the Perl script in a Keyboard Maestro macro:

Replace Numbers in Text with a Calculated Value.kmmacros (4.9 KB)

-Chris

2 Likes