Reverse Word Order of Lines in the Clipboard

That is so Awesome thank you! One more question. I’m using a Shared Apple Notes Note with one of my employees to keep a running list of names we will be contacting at the beginning of the year. So with the above I can switch the names to last name, first name and sort them alphabetically by last name. The note is a checklist so when I copy it, run the macro and then paste it back into the note for my employee to process it has this - [ ] before each name. Is there a final way to remove this prior to pasting it back into the note?

You're quite welcome. I can't take credit for the original macro, but I'm glad I was able to help regardless. And yes, removing an arbitrary string of characters like that is actually very easy. The key action is this:

Sample text:

- Davis, Sharon

  • Feldman, Alicia
  • Jones, Dave
  • Smith, John

Results:

Davis, Sharon
Feldman, Alicia
Jones, Dave
Smith, John

Here's a sample macro to get you started:

[Example] Remove Checklist Formatting.kmmacros (2.8 KB)

1 Like

Oh Awesome - that’s exactly what I need. Love this forum!

1 Like

Hey Folks,

My friend Shane Stanley stumbled across this thread and contributed the following AppleScriptObjC script.

A method called personNameComponentsFromString intelligently parses a string for name components and can make jobs like this more reliable than using regular expressions.

Pretty spiffy.

There are two examples — one for a single name string — and one for a text-list of name-strings.

Of course much customization is possible.

-Chris

------------------------------------------------------------------------------
# Auth: Shane Stanley {Posted by Christopher Stone <scriptmeister@thestoneforge.com>}
# dCre: 2017/11/18 10:36 +1100
# dMod: 2017/11/19 04:37 -0600
# Appl: AppleScriptObjC
# Task: Reversing a name in a string.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @Reversing, @Name, @String
------------------------------------------------------------------------------
use AppleScript version "2.5" --  (10.11) or later
use framework "Foundation"
use scripting additions
------------------------------------------------------------------------------

# Example 1
set theName to "Mr. John K. Smith, jnr."

set massagedName1 to lastNameFirst(theName)

--> "Smith, John"

------------------------------------------------------------------------------

# Example 2
set nameList to text 2 thru -2 of "
John Smith
Dave Jones
Alicia Feldman
Sharon Davis
"
set nameList to paragraphs of nameList

repeat with theNameStr in nameList
   set contents of theNameStr to my lastNameFirst(contents of theNameStr)
end repeat

set AppleScript's text item delimiters to linefeed

return nameList as text

--> "Smith, John
--> Jones, Dave
--> Feldman, Alicia
--> Davis, Sharon"

------------------------------------------------------------------------------
--» HANDLERS
------------------------------------------------------------------------------
on lastNameFirst(nameStr)
   set nf to current application's NSPersonNameComponentsFormatter's new()
   set theComps to nf's personNameComponentsFromString:nameStr
   return (current application's NSString's stringWithFormat_("%@, %@", theComps's familyName(), theComps's givenName())) as text
end lastNameFirst
------------------------------------------------------------------------------
2 Likes

Thank you for that, NSPersonNameComponentsFormatter is a good discovery – I had never noticed it.

In JavaScript for Automation, first an equivalent of your familyName + givenName example, and then an example of pulling anything the $.NSPersonNameComponents has parsed for the following set of fields:

[ 'givenName', 'middleName', 'familyName', 'namePrefix', 'nameSuffix', 'nickname' ]

straight into a JS dictionary for more flexible use.

Example 1 familyName + givenName

(() => {
    
    // lastNameFirst :: String -> String
    const lastNameFirst = strName => {
        const
            nameParts = $.NSPersonNameComponentsFormatter.new
            .personNameComponentsFromString(strName);
        return nameParts.familyName.js + ', ' + nameParts.givenName.js;
    };


    // TEST ------------------------------------------------------------------
    return 'Smith, John\nJones, Dave\nFeldman, Alicia\nDavis Sharon'
        .split('\n')
        .map(lastNameFirst)
        .join('\n');
})();

Example 2 Fuller set of fields -> JS Dictionary

(() => {

    // nameParts :: String -> Dict
    const nameParts = strName => [
            'givenName', 'middleName', 'familyName',
            'namePrefix', 'nameSuffix', 'nickname'
        ].reduce((a, k) => {
            const v = a.components[k].js
            return v ? (a.parts[k] = v, a) : a;
        }, {
            components: $.NSPersonNameComponentsFormatter.new.
            personNameComponentsFromString(strName),
            parts: {}
        })
        .parts

    // TEST ------------------------------------------------------------------
    return [
            'Dr Smith, John Eloysius',
            'Mr Jones, Bertrand David',
            'Feldman, Dame Alicia',
            'Dr Davis, Sharon Rose'
        ]
        .map(nameParts)
})();

to obtain:

[
  {
    "givenName": "John",
    "middleName": "Eloysius",
    "familyName": "Smith",
    "namePrefix": "Dr"
  },
  {
    "givenName": "Bertrand",
    "middleName": "David",
    "familyName": "Jones",
    "namePrefix": "Mr"
  },
  {
    "givenName": "Dame",
    "middleName": "Alicia",
    "familyName": "Feldman"
  },
  {
    "givenName": "Sharon",
    "middleName": "Rose",
    "familyName": "Davis",
    "namePrefix": "Dr"
  }
]
1 Like

PS for a more generic function in AppleScript (an equivalent of the JS nameParts above), I would tend to write to a list of key-value pairs rather than an AS record.

Asking a record/dictionary which fields it does or doesn’t have, (or asking for the value of a field which it doesn’t have) works directly in JavaScript, but brings an additional layer of complexity in AppleScript [1].

Example 3 fuller set of fields in AppleScript

to derive, e.g.

{{{"givenName", "John"}, {"middleName", "Eloysius"}, {"familyName", "Smith"}, 
{"namePrefix", "Dr"}}, 
{{"givenName", "Bertrand"}, {"middleName", "David"}, {"familyName", "Jones"}, 
{"namePrefix", "Mr"}},
{{"givenName", "Alicia"}, {"familyName", "Feldman"}, 
{"namePrefix", "Mrs"}}, 
{{"givenName", "Sharon"}, {"middleName", "Rose"}, {"familyName", "Davis"}, 
{"namePrefix", "Dr"}}}
use framework "Foundation"
use scripting additions

-- nameParts :: String -> [(key, value)]
on nameParts(strName)
    set ca to current application
    script nameField
        on |λ|(a, k)
            set fname to ca's NSSelectorFromString(k)
            set comp to parse of a
            set v to unwrap(comp's performSelector:fname withObject:comp)
            if v ≠ missing value then set end of a's parts to {k, v}
            a
        end |λ|
    end script
    
    set components to ca's NSPersonNameComponentsFormatter's new()'s ¬
        personNameComponentsFromString:strName
    
    parts of foldl(nameField, {parts:{}, parse:components}, ¬
        {"givenName", "middleName", "familyName", ¬
            "namePrefix", "nameSuffix", "nickname"})
end nameParts

-- TEST ----------------------------------------------------------------------
map(nameParts, ({"Dr Smith, John Eloysius", ¬
    "Mr Jones, Bertrand David", ¬
    "Mrs Feldman, Alicia", ¬
    "Dr Davis, Sharon Rose"}))


-- GENERIC FUNCTIONS ---------------------------------------------------------

-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- unwrap :: NSObject -> a
on unwrap(objCValue)
    if objCValue is missing value then
        return missing value
    else
        set ca to current application
        item 1 of ((ca's NSArray's arrayWithObject:objCValue) as list)
    end if
end unwrap

[1] Basic access to AppleScript records when set of fields is variable

-- elems :: Dict -> [a]
on elems(rec)
	set ca to current application
	(ca's NSDictionary's dictionaryWithDictionary:rec)'s allValues() as list
end elems

-- keys :: Dict -> [String]
on keys(rec)
	(current application's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keys

-- Record -> k -> Maybe v
-- mapLookup :: Dict -> k -> Maybe v
on mapLookup(rec, k)
	set ca to current application
	set v to (ca's NSDictionary's dictionaryWithDictionary:rec)'s objectForKey:k
	if v is missing value then
		{nothing:true}
	else
		{nothing:false, just:item 1 of ((ca's NSArray's arrayWithObject:v) as list)}
	end if
end mapLookup

I get an error on this line:

set components to ca's NSPersonNameComponentsFormatter's new()'s ¬
    personNameComponentsFromString:strName

error "-[NSPersonNameComponentsFormatter personNameComponentsFromString:]: unrecognized selector sent to instance 0x7fb16ac72b70" number -10000

running Script Editor 2.8.1 (183.1) on macOS 10.11.6

Thanks for sharing. Do you have an example of how to use these handlers?

Since there are two of us compiling this list of names we may have duplicates. What can be added to look for duplicates and leave the list with only a single incident of name?

Fortunately, this is much easier than you might think. Since you're already using the Unix sort command to alphabetize the names, all you have to do is add a space and -u (the "unique" flag") after it to remove any duplicates:


5 Likes

You're making my job easy! Thanks and Happy Holidays!

2 Likes

Gabe, thanks for sharing the Unix sort command.
I'm not a huge fan of shell scripts, but the simplicity of this make it well worth remembering/using.

While sorting and eliminating dups can be done in both AppleScript and JXA, they require considerably more code (of course, @ccstone will probably prove me wrong. :wink: ).

This is very readable and very easy to use.
I'm adding it to my KMFAM.

1 Like

You’re very welcome! I’m right there with you when it comes to shell scripts, but the sort command, and a few of its flags like -n for sorting numerically (i.e. putting 10 after 2 where it belongs instead of before just because it starts with “1”), -r for sorting in reverse, and of course -u, is an exception to the rule. Even then, I wouldn’t generally think to make use of it if @peternlewis hadn’t made the great call to add input options to the Execute a Shell Script command in KM8 that make it so much easier to work with the exact text you want. In lieu of KM having built-in filters for this kind of sorting and duplicate processing, this is truly the next best thing.

2 Likes

Hey JM,

There are many shell commands just like that — powerful and concise. That's why I decided to learn at least the basics of shell scripting 8 or 9 years ago.

True.

AppleScriptObjC is not as concise as sort, but it's really not bad at all:

------------------------------------------------------------------------------
# Auth: Christopher Stone { Heavy Lifting by Shane Stanley }
# dCre: 2017/11/22 10:55
# dMod: 2017/11/22 10:57
# Appl: AppleScriptObjC
# Task: Sort a list of type text and remove duplicates.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ccstone, @ASObjC, @Sort, @List, @Remove, @Duplicates
------------------------------------------------------------------------------
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
------------------------------------------------------------------------------

set theList to {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "two"}

set sortedUniqueList to SortAndMakeUniqueArray(theList)

------------------------------------------------------------------------------
--» HANDLERS
------------------------------------------------------------------------------
on SortAndMakeUniqueArray(anArray)
   set theSet to current application's NSSet's setWithArray:anArray
   set anArray to theSet's allObjects()
   return (anArray's sortedArrayUsingSelector:"compare:") as list
end SortAndMakeUniqueArray
------------------------------------------------------------------------------

Then of course there's the Satimage.osax:

set theList to {"One", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "two"}
set sortedUniqueList to sortlist theList comparison 1 with remove duplicates

--> {"eight", "five", "four", "nine", "One", "seven", "six", "ten", "three", "two"}

Comparison 1 makes the sort case-insensitive.

-Chris

3 Likes

I, too, am very inexperienced with Regex (and AppleScript, and perl). But I am skilled with shell commands. So I have an alternative using only shell commands. Feel free to ignore this post, I just like making solutions using the shell. About 33% of the shell command below I found online, the remainder was my own creation.

Here's a little bit of AWK.

-Chris


Awk -- Reverse FirstName and LastName.kmmacros (4.9 KB)

I'm always happy when someone comes up with a new, simpler solution. Especially when that person is me. I have a totally different idea that requires no code. Just a simple shell script. Pipe your text into this:

rev | sed 's/$/ ~/' | tr ' ' '\n' | rev | tr '\n' ' ' | tr '~' '\n'

This assumes you don't use the character '~' in your file. I could not find this trick anywhere on the internet so I should score 100 points for this unique solution. I think I should be allowed a little pomposity for coming up with this idea. You can also use a similar, simpler technique to reverse the lines of a file, which I also came up with today, and nobody on the various sites on the internet had this idea either.

rev | tr '\n' '~' | rev | tr '~' '\n'

Oh, there's an extra space at the beginning of most of the results for the first command above. but I'm sure you can figure out how to fix that if you need it fixed.

Do you not consider shell scripts to be code? I wouldn’t categorise them as prose.

When you have time, could you briefly run through what these shell snippets are doing ?

1 Like

Hey @Sleepy,

As far as I can see your code does not perform the job the OP (@slyfox) requested – which was to reverse the name order of lines from input.

John Smith

Becomes:

Smith, John

Simple Sed:

Reverse Name Order of Each Line of Input (Sed).kmmacros (1.9 KB)

Simple Awk:

Reverse Name Order of Each Line of Input (Awk - Simple).kmmacros (1.9 KB)

My original Awk script handles any number of words per line, but this more simple script is tailored to exactly reverse first-name and last-name.

Simple Perl:

Reverse Name Order of Each Line of Input (Perl - Simple).kmmacros (1.9 KB)

-Chris

Since the word "code" seems to be causing a distraction, I drop that claim. Just judge my solution by its simplicity and uniqueness. In my solution you don't see IFs, THENs, LOOPs, or FUNCTIONs, which some of the solutions clearly show. Obviously there's loops required within the rev command to implement it, but it's hidden from the user.

Rev reverses the characters in each line. Tr translates the first character to the second character. \n is the newline character.

Let's look at the second snippet. Step 1. Reverse each line in the file. Step 2. replace all CR's by "~". Step 3. Reverse all the characters in the single merged line. Step 4. Put the CR's back. That reverses the lines in the file perfectly. Pipe any file into that and you will see it reverses the lines. It worked for me in a Terminal command window.

The other one is a bit more complex and I see CJK is refuting that it works so I will answer his question separately.