I think this is a bug, but it could be user error.
In my limited testing, all of the Actions I've tested have accepted an XML update made by the below script, EXCEPT for Display Text in Window. It does NOT accept the change from "TEST__" to "Local__"
Warning! Before you run this script, make sure the below macro is selected in the KM Editor.
### REQUIRES Satimage.osax ###
-- for change command
tell application "Keyboard Maestro"
set oMacro to item 1 of (get selected macros)
set actionList to actions in oMacro
repeat with oAction in actionList
-- set oAction to item 1 of actionList
set actXML to xml of oAction
set actXML2 to change "TEST__" into "Local__" in actXML
--
set xml of oAction to actXML2
set xmlNew to xml of oAction
end repeat -- repeat oAction
end tell
In the below macro, the first Action, Display Text in Window fails to update.
However, the 2nd Action, Display text briefly does update.
###MACRO:Ā Ā Ā Test Case 2 Change Variable Name by Script
Itās not a bug. The text in question is rich text, and you are not altering that in your script, so despite changing the XML, you are not changing the rich text in the action.
I am changing the XML that uses the variable in that Action. If it is included in the rich text, isnāt it redundant to expose it as a separate key?
IAC, the rich text you refer to must be encoded in some manner. It is not the text coding of rich text. How do I convert your encoded rich text to plain text rich text, so I can change, and then convert back to encoded rich text?
The action saves the plain text and rich text for different variants of the action., But it uses only the appropriate one, which for Display Window is the rich text.
It is the NSAttributedString archived to RTF and encoded in data format.
As you know, the XML format used by Keyboard Maestro is an internal format, itās useful in as far as you can use it, and as far as you are willing to accept that it changes over time (and might go away entirely in the future), and is not documented except in as much as XML is relatively self-documenting.
What is causing me the most trouble is that it is base64 encoded.
Actually, the real problem is base64 encoding of multiple lines.
Anyone know how to do this?
Any chance you could provide one of the following to the scripting model:
the RTF text area of an Action either as a separate property that is already decoded
Thanks for the example script.
When I try it, I get the same result.
But when I re-encode the result, the results donāt match the original.
But, if I then decode that, the un-encoded matched your (and my) original result.
But when I re-encode the result, the results donāt match the original.
Reencode with base64? No idea, maybe the line endings are changed? Maybe there are nulls or other characters that are lost. As I said, it is a binary format, so care would need to be taken to ensure there are no encoding errors.
Iād suggest getting /usr/bin/base64 to output to a file, ideally with a command line option, but failing that with a redirection, and then use BBEdit (or otherwise) to hex dump the file to see what characters are really there.
Peter, I have been successful in decoding the RTF for a Display Window Action, changing the KM Variable Names in that, re-encoding, and updating the XML for the Action.
The Action shows the new Variable name properly, but it seems to have lost the RTF of the text block.
Should I expect the Action RTF to be properly updated when the RTF in its XML is updated?
Here's My TEST Script to Update Action 1 XML
2017-10-01 20:33 CT
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
āļøWARNING! Do NOT Use This Script with Production Macro Selected āļø
It CHANGES Your Macro Action.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
VER: 0.2 2017-10-01
Duplicate a Macro you want to Test, and Select the Duplicate
in the KM Editor.
Don't Run this script unless you fully understand it!
Use at your OWN RISK!
*)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
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 xmlNew to my kmUpdateXML(actXML)
set xml to xmlNew
set name to "Updated " & (text 1 thru 10 of ((current date) as «class isot» as string))
end tell
end tell
on kmUpdateXML(pXML)
## REQUIRES Satimage.osax
# This is just for proof-of-concept
local actXML, xmlNew, cmdStr, xmlParts, rtfEncoded, rtfDecoded, rePat, rtfEncodedNew
set actXML to pXML
--- Change the KM Variable Prefix that is in Plain Text in the XML
set actXML to change "TEST__" into "Local__" in actXML
(*
~~~ SPLIT the XML into 3 Parts ~~~
1. From top through:
<key>StyledText</key>
<data>
2. The Encoded RTF in the <data> block
3. From here to END:
</data>
*)
set rePat to "(.+\\<key\\>StyledText\\<\\/key\\>.*\\<data\\>\\n)(.+)(\\n[ \\t]*\\<\\/data\\>.*)"
set xmlParts to my satRegEx(rePat, actXML, {"\\1", "\\2", "\\3"}, {"IGNORECASE", "MULTILINE"})
--- Remove the TABs at the beginning of each line ---
set rtfEncoded to change "^\\t+" into "" in (item 2 of xmlParts) with regexp
--- base64 DECODE ---
set cmdStr to "base64 -D <<EOM" & linefeed & ¬
rtfEncoded & linefeed & "EOM"
set rtfDecoded to do shell script cmdStr
--- Just a double-check to see if this method yields the same result ---
-- not used in further processing.
set cmdStr2 to "echo '" & rtfEncoded & "' | openssl base64 -d"
set rtfDecoded2 to do shell script cmdStr
--- remove non-ascii characters ---
set rtfDecoded to change "[^ -~\\s]+" into "" in rtfDecoded with regexp
--- CHANGE the KM VARIABLE PREFIX ---
set rtfDecoded to change "TEST__" into "Local__" in rtfDecoded
--- base64 ENCODE ---
set cmdStr to "base64 -b 69 <<EOM" & linefeed & ¬
rtfDecoded & linefeed & "EOM"
set rtfEncodedNew to do shell script cmdStr
--- Recombine the XML Parts with the Updated RTF ---
set xmlNew to (item 1 of xmlParts) & rtfEncodedNew & (item 3 of xmlParts)
return xmlNew
end kmUpdateXML
--~~~~~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~~~~
on satRegEx(pstrPattern, pstrTextIn, plstUsing, pFlagList)
(*
PARAMETERS:
⢠pstrPattern string The RegEx pattern to use in the search
⢠pstrTextIn string The stirng to be searched
⢠plstUsing list The Match Groups with optional text to return.
\\0 returns all, \\1 returns Group #1, etc
⢠pFlagList list RegEx Flags (see Satimage Dict for details)
Zero or more of these: (use empty list {} for none)
SINGLELINE, MULTILINE, IGNORECASE, EXTEND, FIND_LONGEST,
FIND_NOT_EMPTY, NEGATE_SINGLELINE, DONT_CAPTURE_GROUP,
NOTBOL, NOTEOL, NEWLINE IN NEGATIVE CC
RETURNS:
⢠The Match(es) that were found
⢠If only 1 Match, then returned as a string
⢠If > 1 Match, then returned as a List of strings.
⢠IF NO Matches, then "[FAILED]" is returned
USING THE SATIMAGE RegEx Find FUNCTION
The key to returning the match of interest is:
⢠using the "string result" keywords at the end
⢠using the "using {"\\1"}" to denote which match group you want returned.
⢠See Satimage Guide to the Regular expressions
http://www.satimage.fr/software/en/smile/text/reg_exp_syntax.html
*)
## -------------- TRY -----------------
try
--- The Satimage RegEx "find text" command Throws an Error if NO Match is found __
set lstRegExResults to find text pstrPattern in pstrTextIn ¬
using plstUsing regexpflag pFlagList with regexp and string result
on error number -2763
set lstRegExResults to {}
end try
## ------------ END TRY -----------------
return lstRegExResults
end satRegEx
Probably. Well, yes, if the XML is valid and the RTF/AttributedString is valid, then the format should be updated. The action is basically replaced.
BTW, each time you archive an NSAttributedString, the data will change - this is just the way NSAttributedString works - Keyboard Maestro actually has code to avoid this to reduce the changes in the macros file (mostly for debugging purposes).
Well, in my test case, the Action is updated, and shows the changed Variable Name, but the RTF is lost.
Probably my error, but you can see my above script which does this.
Please let me know if I need to do something different.
I donāt know what āarchive an NSAttributedStringā means. I assume that is ObjC speak, of which Iām almost illiterate.
All I am doing is changing the Action XML, which has a changed block (which is the RTF encoded).
All of this with conventional AppleScript, using the KM Engine object scripting.
If you just delete the key and data altogether, it will probably default to the text entry, which may be what is happening.
Convert an NSAttributedString to an rtfd.
OK, well, if the rtf is not corrupted, then the format should be preserved. If it is corrupted, then the format might be lost because of the corruption, or because of the fall back to the text.
Iām afraid I canāt look deeply in to this at this time.
@peternlewis, @ShaneStanley offered this script, which fails, and then this question for you:
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How Do I base64 Decode and Encode Multiple Lines?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DATE: 2017-09-30
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/2?u=jmichaeltx
Thatās a property list, so Iād expect something like this to work:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set theString to "<?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>Action</key>
<string>DisplayWindow</string>
<key>MacroActionType</key>
<string>InsertText</string>
<key>StyledText</key>
<data>
cnRmZAAAAAADAAAAAgAAAAcAAABUWFQucnRmAQAAAC5WAQAAKwAAAAEAAABOAQAAe1xy
dGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNDA0XGNvY29hc3VicnRmNDcwCntc
Zm9udHRibFxmMFxmbmlsXGZjaGFyc2V0MCBIZWx2ZXRpY2FOZXVlO30Ke1xjb2xvcnRi
bDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7fQpccGFyZFx0eDU2MFx0eDExMjBcdHgx
NjgwXHR4MjI0MFx0eDI4MDBcdHgzMzYwXHR4MzkyMFx0eDQ0ODBcdHg1MDQwXHR4NTYw
MFx0eDYxNjBcdHg2NzIwXHBhcmRpcm5hdHVyYWxccGFydGlnaHRlbmZhY3RvcjAKClxm
MFxmczI2IFxjZjAgJVZhcmlhYmxlJVRFU1RfX0VsYXBzZWRUaW1lJVwKVEVTVF9fVmFy
MjoJJVZhcmlhYmxlJVRFU1RfX1ZhcjIlfQEAAAAjAAAAAQAAAAcAAABUWFQucnRmEAAA
AJ4Ez1m2AQAAAAAAAAAAAAA=
</data>
<key>Text</key>
<string>%Variable%TEST__ElapsedTime%
TEST__Var2: %Variable%TEST__Var2%</string>
</dict>
</plist>
"
set theString to current application's NSString's stringWithString:theString
set stringData to theString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set {theDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:stringData options:0 |format|:(missing value) |error|:(reference)
if theDict is missing value then error (theError's localizedDescription() as text)
set theData to theDict's objectForKey:"StyledText"
set theAttstring to current application's NSKeyedUnarchiver's unarchiveObjectWithData:theData
This results in theAttstring having a value of āmissing valueā, instead of the expected RTF.
From @ShaneStanley:
I suspect your best bet is to ask Peter why he thinks itās failing. Heās the one putting the data there, so heāll know exactly what to do to extract it.
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How to Update 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
end tell -- oAction
set prefixCur to "TEST__"
set prefixNew to "Local__"
set actXMLRev to my kmUpdateXMLRichText(actXML, prefixCur, prefixNew)
set xml of oAction to actXMLRev
set name of oAction to "AFTER XML Changes"
end tell
--~~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~
on kmUpdateXMLRichText(pXMLStr, pChgFromStr, pChgToStr)
local theString, stringData, mutableDict, theError, theData, mutableAttString, plistData, xmlRevStr
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:"StyledText"
--- 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|()
----------------------------------------------
-- CHANGE All Occurrences of pChgFromStr --
----------------------------------------------
-- modify the mutable atributed string
-- how you do that depends on what you want to do, obviously
set theRange to plainString's rangeOfString:pChgFromStr
mutableAttString's replaceCharactersInRange:theRange withString:pChgToStr
----------------------------------
-- convert back to RTFD data
----------------------------------
set theData to mutableAttString's RTFDFromRange:{0, mutableAttString's |length|()} documentAttributes:(missing value)
-- update the dictionary
mutableDict's setObject:theData forKey:"StyledText"
mutableDict's setObject:(mutableAttString's |string|()) forKey:"Text"
-- make new plist from dictionary ---
set {plistData, theError} to current application's NSPropertyListSerialization's dataWithPropertyList:mutableDict |format|:(current application's NSPropertyListXMLFormat_v1_0) options:0 |error|:(reference)
-----------------------------------
-- GET REVISED XML --
-----------------------------------
-- get text version
set xmlRevStr to (current application's NSString's alloc()'s initWithData:plistData encoding:(current application's NSUTF8StringEncoding)) as text
return xmlRevStr
end kmUpdateXMLRichText