I am trying to search and replace text in a RTF. I have it working fine in plain text, but can’t see how to retain the formatting etc? Can anyone help?
tell application id "com.culturedcode.ThingsMac"
set sel to item 1 of selected to dos
set theURL to notes of selected to dos as string
set theURLlength to count theURL
set the theUUID to characters 21 thru theURLlength of theURL as string
end tell
tell application id "DNtp"
set thisItem to get record with uuid theUUID
set tagList to tags of thisItem
set itemText to get plain text of thisItem
set plain text of thisItem to findAndReplaceInText(itemText, "#ToDo ", "#ToDoComplete ") of me
end tell
on findAndReplaceInText(theText, theSearchString, theReplacementString)
set AppleScript's text item delimiters to theSearchString
set theTextItems to every text item of theText
set AppleScript's text item delimiters to theReplacementString
set theText to theTextItems as string
set AppleScript's text item delimiters to ""
return theText
end findAndReplaceInText
You’ve already seen why this is not going to work the way you want it to. There are ways using Text Suite and the text property of paragraphs, but that’s a PITA. RTF is a pure text format on the surface, but in fact it uses formatting interspersed with text. Changing the underlying text by changing plain text probably messes up the whole structure since RTF is no plain text in the classical sense. There’s already been a similar discussion here
Use Markdown for that, nowadays it even has boxes that can be used for to-dos. That is a real text format, i.e. the mark up is very limited, very simple and very easy to parse. And replacing text in the plain text of a MD file does not break anything, since formatting is done externally, via CSS.
@chrillek thanks for your reply. I am just interested to see if anyone else had any thoughts or workarounds. It’s strange that Textedit supports it and my understand is that the RTF in DT seems to use that format?
-- Replace text in RTF(D) documents
-- NOTE: This script can't replace text having mixed styles and is by default case-sensitive (see 'considering case')
-- WARNING: This can't be undone!
tell application id "DNtp"
try
if (count of selected records) is 0 then error "Please select some documents."
repeat
set search_string to display name editor "Replace Text" info "Enter text to find:"
if search_string is not "" then exit repeat
end repeat
set replacement_string to display name editor "Replace Text" info "Enter replacement text:"
repeat with theRecord in selected records
if type of theRecord is rtf or type of theRecord is rtfd then
tell text of theRecord
repeat with theAttributeRun in attribute runs
set theString to (theAttributeRun as string)
considering case -- Alternative: ignoring case
if theString contains search_string then
set {od, text item delimiters of AppleScript} to {text item delimiters of AppleScript, search_string}
set theString to text items of theString
set text item delimiters of AppleScript to replacement_string
set theString to "" & theString
set text item delimiters of AppleScript to od
set text of theAttributeRun to theString
end if
end considering
end repeat
end tell
end if
end repeat
on error error_message number error_number
if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
end try
end tell
And here’s a version that supports both plain & rich text documents:
-- Replace text in plain and rich text documents
-- NOTE: This script can't replace text having mixed styles and is by default case-sensitive (see 'considering case')
-- WARNING: This can't be undone!
tell application id "DNtp"
try
if (count of selected records) is 0 then error "Please select some documents."
repeat
set search_string to display name editor "Replace Text" info "Enter text to find:"
if search_string is not "" then exit repeat
end repeat
set replacement_string to display name editor "Replace Text" info "Enter replacement text:"
considering case -- Alternative: ignoring case
repeat with theRecord in selected records
if type of theRecord is rtf or type of theRecord is rtfd then
tell text of theRecord
repeat with theAttributeRun in attribute runs
set theString to (theAttributeRun as string)
if theString contains search_string then
set theString to my replaceText(theString, search_string, replacement_string)
set text of theAttributeRun to theString
end if
end repeat
end tell
else if type of theRecord is txt then
set theString to plain text of theRecord
if theString contains search_string then
set theString to my replaceText(theString, search_string, replacement_string)
set plain text of theRecord to theString
end if
end if
end repeat
end considering
on error error_message number error_number
if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
end try
end tell
on replaceText(theString, find, replace)
local od
set {od, text item delimiters of AppleScript} to {text item delimiters of AppleScript, find}
set theString to text items of theString
set text item delimiters of AppleScript to replace
set theString to "" & theString
set text item delimiters of AppleScript to od
return theString
end replaceText
Ok, it’s attribute runs, not paragraphs where you have to change the text attribute.
If you have a minute (ok, bad joke), could you explain why one would use one and not the other? Or maybe just post a link to a relevant document.
Thanks a lot . I still think it’s convoluted
Setting the text of a paragraph changes the style (“attribute run”) of the complete paragraph to the style at the beginning, therefore this is not suitable in case of paragraphs having mixed styles.
Here’s a case insensitive version which is in case of RTF(D) also noticeably faster:
-- Replace text in plain and rich text documents (case insensitive)
-- NOTES:
-- 1. This script can't replace text having mixed styles
-- 2. The replacement text can't contain the search text
-- 3. But it's about 5 times faster in case of RTF(D) than the case sensitive version
-- WARNING: This can't be undone!
-- Created by Christian Grunenberg Thu Jul 27 2021.
-- Copyright (c) 2021. All rights reserved.
tell application id "DNtp"
try
set theNum to count of selected records
if theNum is 0 then error "Please select some documents."
repeat
set search_string to display name editor "Replace Text" info "Enter text to find:"
if search_string is not "" then exit repeat
end repeat
set replacement_string to display name editor "Replace Text" info "Enter replacement text:"
ignoring case
if replacement_string contains search_string then error "Replacement text can't contain search string"
show progress indicator "Replacing Text" steps theNum
repeat with theRecord in selected records
step progress indicator (name of theRecord) as string
if type of theRecord is rtf or type of theRecord is rtfd then
try
tell text of theRecord
repeat while true
-- We can't retrieve all attribute ones at once as changing the first one invalidates the
-- retrieved values. In addition, the results are always case insensitive and
-- don't honor considering/ignoring case
tell (attribute run 0 whose text 1 contains search_string)
set theString to my replaceText(text 1 as string, search_string, replacement_string)
set text 1 to theString
end tell
end repeat
end tell
end try
else if type of theRecord is txt then
set theString to plain text of theRecord
if theString contains search_string then
set theString to my replaceText(theString, search_string, replacement_string)
set plain text of theRecord to theString
end if
end if
end repeat
hide progress indicator
end ignoring
on error error_message number error_number
hide progress indicator
if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
end try
end tell
on replaceText(theString, find, replace)
local od
set {od, text item delimiters of AppleScript} to {text item delimiters of AppleScript, find}
set theString to text items of theString
set text item delimiters of AppleScript to replace
set theString to "" & theString
set text item delimiters of AppleScript to od
return theString
end replaceText
Those of us who don’t like scripting would just export to Nisus Writer Pro, which reads and writes almost “native” RTF and has a superb search and replace. Too bad if you missed it in the SummerFest Sale.
Edgar