KM8: How Can I get Plain Text from a Comment Action?

I love KM8, but it has broken one of my major workflows: Posting of ReleaseNotes with my macro uploads.

In virtually every Macro I write, I have a Comment Action at the top, which contains my ReleaseNotes for that Macro. Prior to KM8, I could read the plain text of the Comment Action in a script, and then paste into a KM Forum topic where I am upload my macro.

KM8: How Can I get Plain Text from a Comment Action?

Now the Comment Action uses Rich Text, which is available in an encoded format in the XML of the Action. But I have been unsuccessful in converting that encoded rich text into plain text.

@peternlewis, may I request that you provide us with a scripting command that:

  • Returns plain text for any selected “<styled text>” element in the XML.
  • Return rich text as plain text (like HTML) that can be edited.
  • Sets/Updates the Action from a revised rich text string.

As you know from our discussion in KM8: XML of Display Text in Window Does Not Update by Script, this is not something that is easly converted (decoded, encoded) using AppleScript. So any help you can give us would be greatly appreciated.

If there is an ASObjC or Bash script that will do the job now, I’m fine with that. My problem is that I don’t know enough about either to write such scripts.

Here is an example XML from a KM8 Comment Action:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>ActionColor</key>
	<string>Green</string>
	<key>ActionName</key>
	<string>~~~ ReleaseNotes for  VER: 1.0    DATE ~~~</string>
	<key>IsDisclosed</key>
	<false/>
	<key>MacroActionType</key>
	<string>Comment</string>
	<key>StyledText</key>
	<data>
	cnRmZAAAAAADAAAAAgAAAAcAAABUWFQucnRmAQAAAC65AgAAKwAAAAEAAACxAgAAe1xy
	dGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNDA0XGNvY29hc3VicnRmNDcwCntc
	Zm9udHRibFxmMFxmbmlsXGZjaGFyc2V0MCBWZXJkYW5hO30Ke1xjb2xvcnRibDtccmVk
	MjU1XGdyZWVuMjU1XGJsdWUyNTU7fQpccGFyZFx0eDU2MFx0eDExMjBcdHgxNjgwXHR4
	MjI0MFx0eDI4MDBcdHgzMzYwXHR4MzkyMFx0eDQ0ODBcdHg1MDQwXHR4NTYwMFx0eDYx
	NjBcdHg2NzIwXHBhcmRpcm5hdHVyYWxccGFydGlnaHRlbmZhY3RvcjAKClxmMFxmczI4
	IFxjZjAgQXV0aG9yLkBKTWljaGFlbFRYXApcCioqUFVSUE9TRToqKlwKXAoqICoqVEJE
	KipcClwKKipSRVFVSVJFUzoqKlwKXAoxLiAqKktNIDguMC4yKyoqXAogICogQnV0IGl0
	IGNhbiBiZSB3cml0dGVuIGluIEtNIDcuMy4xK1wKICAqIEl0IGlzIEtNOCBzcGVjaWZp
	YyBqdXN0IGJlY2F1c2Ugc29tZSBvZiB0aGUgQWN0aW9ucyBoYXZlIGNoYW5nZWQgdG8g
	bWFrZSB0aGluZ3Mgc2ltcGxlciwgYnV0IGVxdWl2YWxlbnQgQWN0aW9ucyBhcmUgYXZh
	aWxhYmxlIGluIEtNIDcuMy4xLlwKLlwKMi4gKiptYWNPUyAxMC4xMS42IChFbCBDYXBp
	dGFuKSoqXAogICogS00gOCBSZXF1aXJlcyBZb3NlbWl0ZSBvciBsYXRlciwgc28gdGhp
	cyBtYWNybyB3aWxsIHByb2JhYmx5IHJ1biBvbiBZb3NlbWl0ZSwgYnV0IEkgbWFrZSBu
	byBndWFyYW50ZWVzLiAgOndpbms6IH0BAAAAIwAAAAEAAAAHAAAAVFhULnJ0ZhAAAAAG
	mtJZtgEAAAAAAAAAAAAA
	</data>
	<key>Title</key>
	<string></string>
</dict>
</plist>

The plain text looks like this:

Author.@JMichaelTX

**PURPOSE:**

* **TBD**

**REQUIRES:**

1. **KM 8.0.2+**
  * But it can be written in KM 7.3.1+
  * It is KM8 specific just because some of the Actions have changed to make things simpler, but equivalent Actions are available in KM 7.3.1.
.
2. **macOS 10.11.6 (El Capitan)**
  * KM 8 Requires Yosemite or later, so this macro will probably run on Yosemite, but I make no guarantees.  :wink: 

This is just a brief example of my normal ReleaseNotes.

Example Script to Get XML of First Action of a Selected Macro

tell application "Keyboard Maestro"
  set oMacro to item 1 of (get selected macros)
  
  set actionList to actions in oMacro
  set oAction to item 1 in actionList
  
  tell oAction
    set actXML to xml
  end tell -- oAction
  
end tell -- KM

set the clipboard to actXML
return actXML

You guys are hilarious! :slight_smile:

Crowd: We want rich text in out comments
Me: Ok, here you go
Crowd: Ahh, now we can’t mess around with them
Me: sigh.

:slight_smile:

It should be possible to write some JXA code that reads the RFD data in the XML and returns the string as plain text, or that generates the string from plain text and returns the RFD data.

Unfortunately, I’m not all that familiar with JXA either, so I don’t really know how to do it. The Objective C is:

Data to plain string:

[[[NSAttributedString alloc] initWithRTFD:data documentAttributes:nil] string];

String to data:

[[[NSAttributedString alloc] initWithString:s] RTFDFromRange:NSMakeRange(0,s.length) documentAttributes:@{}];

If they were translated to JXA then presumably that would be able to encode/decode the data to/from plain text (losing any formatting, obviously).

It’s not something I can add to Keyboard Maestro as such.

1 Like

Thanks. Then my night job as stand-up comic is secure! LOL
Not sure who the crowd is/was. I might have been a bystander cheering on, but I suspect that Mr. DanT was somehow involved. Not blaming him. Other than the loss of getting plain text out of it, I think it is a good idea.

I don't think JavaScript has any utility here. I've done some research and have not found any native JS functions for this. I've talked a bit to Dan, and his suggestion was to try bash base64. But it now clear, from my discussions with Shane, that that is not the proper approach.

More and more I'm becoming convinced that, if you can't provide it via the scripting model, then a ASObjC script is the best bet. I'll pass on what you posted to Shane, and see if he has any ideas/solutions.

Thanks for looking at this.

JXA can use all the Objective-C foundation stuff as far as I understand it (same sort of thing as ASObjC).

Like this:

The example you gave does show use of ObjC with JXA, but the script is of no help to us in solving this issue of getting plain text from a Comment Action.

The problem is use of ObjC with JXA is fairly rare, and there are few good examples. There are also some limitations with JXA that you don't have with AppleScript. OTOH, there are many examples of ASObjC, some right here in this forum.

IAC, the most knowledgeable person I know that is a real expert in scripting ObjC is Shane Stanley, and he only uses AppleScript. He has even written a book on it:
Everyday AppleScriptObjC.

Peter, as I mentioned in my related topic:

  1. Allows us to read, decode, change, and update the rich text in a KM Action
  2. Convert the rich text to plain text.

So, both issues have now been solved!

Many, many thanks. First, for exposing the macro and action objects to scripting. Then for your outstanding help and patience in answering all my questions, and providing the key code we needed for a solution.

I have to say, to everyone that might be reading this, Peter, as a developer, provides the most outstanding support for his product of any developer I've known in 30 years! No one else even comes close!

My TEST Script to Demo Getting Plain Text (NOT suitable for Production Use)

This was enabled by your above ObjC code, and is 99% Shane's code.
I made a few changes to refactor into a handler and add some error handling.
All errors are mine.

(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
How to Get Plain Text from Rich Text in KM Action
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DATE:    2017-10-03
AUTHOR: ShaneStanley
REF:
  • How Do I base64 Decode and Encode Multiple Lines?
    • Late Night Software Ltd., 
  • http://forum.latenightsw.com/t/how-do-i-base64-decode-and-encode-multiple-lines/759/11?u=jmichaeltx

The part this doesn’t really cover is how to edit the styled text (attributed string), which can be complicated depending on what you want to do. Assuming you don’t want to change the attributes themselves, the methods you’d use are replaceCharactersInRange:withString: and deleteCharactersInRange:. You get the ranges you use based on the unstyled text, as above.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

tell application "Keyboard Maestro"
  set oMacro to item 1 of (get selected macros)
  
  set actionList to actions in oMacro
  set oAction to item 1 in actionList
  
  tell oAction
    set actXML to xml
    set actionName to name
  end tell -- oAction
  
  set actPlainText to my kmActionRichTextToPlainText(actXML, actionName)
  
end tell

return actPlainText

--~~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~

on kmActionRichTextToPlainText(pXMLStr, pActionName)
  
  local theString, stringData, mutableDict, theError, theData, mutableAttString, plainString, richTextKeyStr
  
  set LF to linefeed
  
  --- Dictionary Key for Rich Text Block ---
  set richTextKeyStr to "StyledText"
  
  set theString to pXMLStr
  
  set theString to current application's NSString's stringWithString:theString
  
  -- convert string to data
  set stringData to theString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
  
  -- convert plist to mutable dictionary
  set {mutableDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:stringData options:(current application's NSPropertyListMutableContainersAndLeaves) |format|:(missing value) |error|:(reference)
  
  if mutableDict is missing value then error (theError's localizedDescription() as text)
  
  -- extract RTFD data and convert to a mutable atributed string
  set theData to mutableDict's objectForKey:richTextKeyStr
  
  if (theData is not missing value) then
    
    --- Decode Rich Text ---
    set mutableAttString to current application's NSMutableAttributedString's alloc()'s initWithRTFD:theData documentAttributes:(missing value)
    
    --- GET PLAIN TEXT from Rich Text ---
    set plainString to (mutableAttString's |string|()) as text  ## as text needed to run in KM
    
  else -- ERROR: "StyledText" key was NOT found
    
    set plainString to ""
    
    set msgStr to "❯❯❯ ERROR ❮❮❮" & LF & ¬
      "'StyledText' key was NOT found in XML for Action: " & pActionName
    set titleStr to "Handler: kmActionRichTextToPlainText"
    
    beep
    
    display dialog msgStr ¬
      with title titleStr ¬
      buttons {"Cancel", "OK"} ¬
      default button ¬
      "OK" cancel button ¬
      "Cancel" with icon stop
    
    
  end if
  
  return plainString
  
end kmActionRichTextToPlainText
4 Likes

I had to revise the script in order to run in a KM Execute AppleScript Action.
I had to add the as text to this line:
set plainString to (mutableAttString's |string|()) as text