KM8: XML of Display Text in Window Does Not Update by Script

km8
applescript

#1

I think this is a bug, but it could be user error. :wink:

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__"

:exclamation: 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

~~~ VER: 1.0    2017-09-29 ~~~

####DOWNLOAD:
Test Case 2 Change Variable Name by Script.kmmacros (2.3 KB)
Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.



KM8: How Can I get Plain Text from a Comment Action?
Easy Way to Convert Variables to Local Variables?
#2

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.


#3

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?


#4

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.

But it is what it is.


#5

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:

  1. the RTF text area of an Action either as a separate property that is already decoded
  2. The Action XML with all data already decoded
  3. A command that will base64 decode and encode

Just asking. :smile:


#6

1: No, 2: No. 3: Maybe.

/usr/bin/base64 is installed by default I believe.

base64 -D <<EOM
cnRmZAAAAAADAAAAAgAAAAcAAABUWFQucnRmAQAAAC5LAQAAKwAAAAEAAABDAQAAe1xy
dGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNDA0XGNvY29hc3VicnRmNDcwCntc
Zm9udHRibFxmMFxmbmlsXGZjaGFyc2V0MCBIZWx2ZXRpY2FOZXVlO30Ke1xjb2xvcnRi
bDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDBcZ3JlZW4wXGJsdWUwO30KXHBh
cmRcdHg1NjBcdHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBc
dHg0NDgwXHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxwYXJkaXJuYXR1cmFsXHBh
cnRpZ2h0ZW5mYWN0b3IwCgpcZjBcZnMyNiBcY2YyIFRFU1RfX1ZhcjM6CSVWYXJpYWJs
ZSVURVNUX19WYXIzJX0BAAAAIwAAAAEAAAAHAAAAVFhULnJ0ZhAAAABoCs9ZtgEAAAAA
AAAAAAAA
EOM
rtfdTXT.rtf.K+C{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\f0\fs26 \cf2 TEST__Var3:	%Variable%TEST__Var3%}#TXT.rtfh
?Y?

It’s a binary format I believe, so I’m not sure that Search & Replace will be entirely safe.


#7

Peter,

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.

Can I use this to update the XML?

my script to encode your results

base64 <<EOM
rtfdTXT.rtf.K+C{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\f0\fs26 \cf2 TEST__Var3:%Variable%TEST__Var3%}#TXT.rtfh
?Y?
EOM
cnRmZFRYVC5ydGYuSytDe1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNDA0XGNvY29hc3VicnRmNDcwCntcZm9udHRibFxmMFxmbmlsXGZjaGFyc2V0MCBIZWx2ZXRpY2FOZXVlO30Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDBcZ3JlZW4wXGJsdWUwO30KXHBhcmRcdHg1NjBcdHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgwXHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxwYXJkaXJuYXR1cmFsXHBhcnRpZ2h0ZW5mYWN0b3IwClxmMFxmczI2IFxjZjIgVEVTVF9fVmFyMzolVmFyaWFibGUlVEVTVF9fVmFyMyV9I1RYVC5ydGZoCj9ZPwo=

since that output the results as one line, and I noticed your input is 69 char/line, I tried this:
###Break Encoded Result into Lines

base64 -b 69 <<EOM
rtfdTXT.rtf.K+C{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\f0\fs26 \cf2 TEST__Var3:%Variable%TEST__Var3%}#TXT.rtfh
?Y?
EOM
cnRmZFRYVC5ydGYuSytDe1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNDA0X
GNvY29hc3VicnRmNDcwCntcZm9udHRibFxmMFxmbmlsXGZjaGFyc2V0MCBIZWx2ZXRpY2
FOZXVlO30Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDBcZ3J
lZW4wXGJsdWUwO30KXHBhcmRcdHg1NjBcdHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAw
XHR4MzM2MFx0eDM5MjBcdHg0NDgwXHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxwY
XJkaXJuYXR1cmFsXHBhcnRpZ2h0ZW5mYWN0b3IwClxmMFxmczI2IFxjZjIgVEVTVF9fVm
FyMzolVmFyaWFibGUlVEVTVF9fVmFyMyV9I1RYVC5ydGZoCj9ZPwo=

same results as before, just separated on lines.

When I now take those encoded results, and decode, I get:

Decode Results from Last Step

base64 -D <<EOM
cnRmZFRYVC5ydGYuSytDe1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNDA0X
GNvY29hc3VicnRmNDcwCntcZm9udHRibFxmMFxmbmlsXGZjaGFyc2V0MCBIZWx2ZXRpY2
FOZXVlO30Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDBcZ3J
lZW4wXGJsdWUwO30KXHBhcmRcdHg1NjBcdHgxMTIwXHR4MTY4MFx0eDIyNDBcdHgyODAw
XHR4MzM2MFx0eDM5MjBcdHg0NDgwXHR4NTA0MFx0eDU2MDBcdHg2MTYwXHR4NjcyMFxwY
XJkaXJuYXR1cmFsXHBhcnRpZ2h0ZW5mYWN0b3IwClxmMFxmczI2IFxjZjIgVEVTVF9fVm
FyMzolVmFyaWFibGUlVEVTVF9fVmFyMyV9I1RYVC5ydGZoCj9ZPwo=
EOM
rtfdTXT.rtf.K+C{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470
{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\f0\fs26 \cf2 TEST__Var3:%Variable%TEST__Var3%}#TXT.rtfh
?Y?

Which is the same as your original decoded results.

I don’t understand this. How can two different inputs yield the same output?


#8

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.


#9

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

updated 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

#10

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).


#11

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. :wink:

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.


#12

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.


#13

@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.

Thanks for your help.


#14

It’s not that kind of archiving. It doesn’t use NSKeyedUnarchiver. Or maybe it does, I have no idea.

The code I use to read the StyledText data is:

		NSAttributedString* result = [[NSAttributedString alloc] initWithRTFD:data documentAttributes:nil];

#15

@peternlewis,

Many thanks for your help and patience in solving this problem.
The key was the ObjC code you provided here:

Thanks to you and @ShaneStanley, we now have a complete solution that does all of the following:

  • Converts the KM Action XML into an ASObjC Dictionary object
  • Extracts the "<StyledText>" section, which contains the RTFD of the action
  • Converts that into "normal", updatable, rich text
  • Updates the rich text, changing the variable names in it.
  • Updates the Dictionary object, and saves it to a new XML string.
  • Using that XML string, the KM Action object is updated.

Shanes code also provides a plain text version of the rich text. So that will solve the other issue I had with getting plain text from a Comment Action.

Here's my test script (NOT suitable for production use)

Based on Shane's code here, modified by me to refactor into handler
Therefore, any errors are mine.

It was tested using this Action:

and produced this result:

(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
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

KM8: How Can I get Plain Text from a Comment Action?
Use a named clipboard that changes name based on the iteration of my FOR EACH loop