This script splits RTF(D) records at a delimiter.
It optionally changes the font and font size.
Setup
You can change every property.
-
property
changeFont
: Iftrue
then the resulting records’ font and font size is changed. -
property
theFontSize
: The desired font size of the body text, e.g.14.0
If set to0
then DEVONthink’s rich text font size is used (seePreferences > Edit
) -
property
theFontName
: The desired font’s family name, e.g."Lucida Grande"
If set to""
then DEVONthink’s rich text font is used (seePreferences > Edit
) -
property
theMonoFontName
: The desired mono spaced font’s family name, e.g."Courier"
If set to""
then only the font size of mono spaced fonts is changed
(There’s no DEVONthink preference that could be used, so if you want to change mono spaced fonts you’ll have to set this property)
Usage
-
Insert the delimiter
§§§
wherever you want to split a record.
It’s not necessary to insert the delimiter at the beginning or end.
Delimiters can be placed between lines and also inside. -
Run script
-- Split RTF(D) at Delimiter
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
property theDelimiter : "§§§" -- insert this delimiter anywhere in a record
property changeFont : true -- set this to false if you don't want to change the font
property theFontSize : 0 -- if 0 DEVONthink's rich text font size is used (see preferences > edit). To use another size set this property to e.g. 14.0
property theFontName : "" -- if "" DEVONthink's rich text font is used (see preferences > edit). To use another font set this property to e.g. "Lucida Grande"
property theMonoFontName : "" -- if "" only the mono spaced font's size is changed. To use another mono spaced font set this property to e.g. "Courier"
tell application id "DNtp"
try
set theRecords to selected records whose type = rtfd or type = rtf
if theRecords = {} then my displayReminder()
repeat with thisRecord in theRecords
set thisRecord_Path to path of thisRecord
set thisRecord_Name to name without extension of thisRecord
my splitRTFDatDelimiter(thisRecord_Path, thisRecord_Name, thisRecord)
end repeat
hide progress indicator
on error error_message number error_number
hide progress indicator
activate
if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
return
end try
end tell
on splitRTFDatDelimiter(theRecord_Path, theRecord_Name, theRecord)
try
if changeFont then
set theProgressSteps to 4
else
set theProgressSteps to 2
end if
set theAttributedString to my readAttributedString(theRecord_Path)
set theAttributedString_Length to theAttributedString's |length|()
if theAttributedString_Length = 0 then return
--------------------------------------------------------------- Change Font ---------------------------------------------------------------------
if changeFont then
---------------------------------------------------------- Get Font Attributes ---------------------------------------------------------------
tell application id "DNtp" to show progress indicator "Analyzing... " & theRecord_Name steps theProgressSteps with cancel button
tell application id "DNtp" to step progress indicator
set theLocation to 0
set theAttribute to current application's NSFontAttributeName
set theAttributesArray to current application's NSMutableArray's new()
repeat while (theLocation < theAttributedString_Length)
set {thisFont, thisFontRange} to theAttributedString's |attribute|:theAttribute atIndex:(theLocation) longestEffectiveRange:(reference) inRange:{location:theLocation, |length|:(theAttributedString_Length - theLocation)}
theAttributesArray's addObject:{|Font|:thisFont, FontRange:thisFontRange, FontSize:(thisFont's pointSize())}
set theLocation to theLocation + (thisFontRange's |length|)
end repeat
---------------------------------------------------------- Analyze Font Sizes ---------------------------------------------------------------
set theFontSizes to ((theAttributesArray's valueForKey:"FontSize")'s valueForKeyPath:"@distinctUnionOfObjects.self")'s sortedArrayUsingDescriptors:{(current application's NSSortDescriptor's sortDescriptorWithKey:"self" ascending:false selector:"compare:")}
set theFontSizes_SortedTotalLengthsArray to current application's NSMutableArray's new()
repeat with i from 0 to ((theFontSizes's |count|()) - 1)
set thisFontSize to (theFontSizes's objectAtIndex:i)
set thisFontSizeTotalLength to (((theAttributesArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontSize = " & (thisFontSize))))'s valueForKeyPath:"FontRange.length")'s valueForKeyPath:"@sum.self")
(theFontSizes_SortedTotalLengthsArray's addObject:{FontSize:thisFontSize, TotalLength:thisFontSizeTotalLength})
end repeat
theFontSizes_SortedTotalLengthsArray's sortUsingDescriptors:{(current application's NSSortDescriptor's sortDescriptorWithKey:"TotalLength" ascending:false selector:"compare:")}
set theFontSize_Body to (((theFontSizes_SortedTotalLengthsArray)'s firstObject())'s valueForKey:"FontSize") as real
------------------------------------------------------------- Change Font --------------------------------------------------------------------
tell application id "DNtp" to show progress indicator "Changing Font... " & theRecord_Name steps theProgressSteps with cancel button
tell application id "DNtp" to step progress indicator
if theFontName = "" or theFontSize = 0 then
if current application's id = "com.devon-technologies.think3" then -- https://forum.latenightsw.com/t/storing-persistent-values-in-preferences/864
set theDefaults to current application's NSUserDefaults's standardUserDefaults()
else
set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.devon-technologies.think3"
end if
if theFontName = "" then set theFontName to (theDefaults's dictionaryRepresentation())'s stringForKey:"RichFontName"
if theFontSize = 0 then set theFontSize to (theDefaults's dictionaryRepresentation())'s doubleForKey:"RichPointSize"
end if
set theFont to current application's NSFont's fontWithName:(theFontName) |size|:theFontSize
if theFont = missing value then error "Please provide a valid Font Name"
set theFont_FamilyName to theFont's familyName()
if theMonoFontName ≠ "" then
set theMonoFont to current application's NSFont's fontWithName:(theMonoFontName) |size|:theFontSize
if (theMonoFont = missing value) or not ((theMonoFont's isFixedPitch()) as boolean) then error "Please provide a valid Mono Font Name"
set theMonoFont_FamilyName to theMonoFont's familyName()
else
set theMonoFont_FamilyName to missing value
end if
set theFontSize_Ratio to (theFontSize / theFontSize_Body)
set theFontManager to current application's NSFontManager's sharedFontManager()
set theAttribute to current application's NSFontAttributeName
set theMutableAttributedString to theAttributedString's mutableCopy()
repeat with i from 0 to ((theAttributesArray's |count|()) - 1)
set thisItem to (theAttributesArray's objectAtIndex:i)
set thisFont to (thisItem's valueForKey:"Font")
set thisFontRange to (thisItem's valueForKey:"FontRange")
set thisFontSize to (thisItem's valueForKey:"FontSize") as real
set thisNewFontSize to thisFontSize * theFontSize_Ratio
set thisNewFont to (theFontManager's convertFont:(thisFont) toSize:thisNewFontSize)
if not ((thisFont's isFixedPitch()) as boolean) then
set thisNewFont to (theFontManager's convertFont:thisNewFont toFamily:theFont_FamilyName)
else
if theMonoFont_FamilyName ≠ missing value then
set thisNewFont to (theFontManager's convertFont:thisNewFont toFamily:theMonoFont_FamilyName)
end if
end if
(theMutableAttributedString's removeAttribute:theAttribute range:thisFontRange)
(theMutableAttributedString's addAttribute:theAttribute value:thisNewFont range:thisFontRange)
end repeat
set theAttributedString to theMutableAttributedString
end if
---------------------------------------------------------- Get Substring Ranges ----------------------------------------------------------------
if theDelimiter = "§§§" then
set theDelimiter_escaped to theDelimiter
else
set theDelimiter_String to current application's NSString's stringWithString:theDelimiter
set theDelimiter_escaped to (theDelimiter_String's stringByReplacingOccurrencesOfString:("\\,|\\!|\\?|\\.|\\(|\\)|\\[|\\]|\\{|\\}|\\*|\\\\|\\^|\\+|\\<|\\>|\\||\\$|\\=") withString:("\\\\$0") options:(current application's NSRegularExpressionSearch) range:{location:0, |length|:theDelimiter_String's |length|()}) as string
end if
set {theRegex, theError} to current application's NSRegularExpression's regularExpressionWithPattern:("(?ms:(?<=" & theDelimiter_escaped & "|\\A)(.*?)(?=" & theDelimiter_escaped & "|\\Z))") options:0 |error|:(reference)
if theError ≠ missing value then error (theError's localizedDescription() as string)
set theAttributedString_String to theAttributedString's |string|()
set theMatches to (theRegex's matchesInString:theAttributedString_String options:0 range:{0, theAttributedString_String's |length|()})
if theMatches's |count|() < 2 then
tell application id "DNtp" to log message info "Script \"Split RTFD at Delimiter\": No Delimiter" record theRecord
return
end if
------------------------------------------------------ Write Temp Files & Import --------------------------------------------------------------
tell application id "DNtp"
show progress indicator "Splitting... " & theRecord_Name steps theProgressSteps as string with cancel button
step progress indicator
set theGroup to create record with {name:(theRecord_Name & " [Split at Delimiter]"), type:group} in parent 1 of theRecord
end tell
set theTempDirectoryURL to my createTempDirectory()
set theCharacterSet to (current application's NSCharacterSet's alphanumericCharacterSet())'s invertedSet()
repeat with i from 0 to ((theMatches's |count|()) - 1)
set {thisAttributedString, thisTempURL} to my writeAttributedStringToTempFile(theAttributedString, (((theMatches's objectAtIndex:i)'s rangeAtIndex:0)), theTempDirectoryURL)
set thisTempPath to (thisTempURL's |path|()) as string
set thisName to ((((((thisAttributedString's |string|())'s stringByTrimmingCharactersInSet:theCharacterSet))'s componentsSeparatedByString:linefeed)'s firstObject())'s stringByTrimmingCharactersInSet:theCharacterSet) as string
if thisName = "" then set thisName to "Untitled"
tell application id "DNtp" to import thisTempPath name thisName to theGroup
end repeat
set {successDeleteDir, theError} to (current application's NSFileManager's defaultManager()'s removeItemAtURL:(theTempDirectoryURL) |error|:(reference))
if theError ≠ missing value then error (theError's localizedDescription() as string)
tell application id "DNtp" to step progress indicator
tell application id "DNtp" to hide progress indicator
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"splitRTFDatDelimiter\"" message error_message as warning
try
current application's NSFileManager's defaultManager()'s removeItemAtURL:(theTempDirectoryURL) |error|:(missing value)
end try
error number -128
end try
end splitRTFDatDelimiter
on displayReminder()
activate
display alert "Split RTF(D)" message linefeed & "Insert delimiter " & theDelimiter & " anywhere in a record, e.g.:" & linefeed & linefeed & "Chapter 1" & linefeed & theDelimiter & linefeed & "Chapter 2" & linefeed ¬
& linefeed & "or:" & linefeed & linefeed & "This is " & theDelimiter & " a line." & linefeed as informational
error number -128
end displayReminder
on readAttributedString(thePath)
try
set {theAttributedString, theError} to current application's NSAttributedString's alloc()'s initWithURL:(current application's |NSURL|'s fileURLWithPath:thePath) options:(missing value) documentAttributes:({NSDocumentTypeDocumentAttribute:(current application's NSRTFDTextDocumentType)}) |error|:(reference)
if theError ≠ missing value then error (theError's localizedDescription() as string)
return theAttributedString
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"readAttributedString\"" message error_message as warning
error number -128
end try
end readAttributedString
on createTempDirectory()
try
set theTempDirectoryURL to current application's |NSURL|'s fileURLWithPath:((current application's NSTemporaryDirectory())'s stringByAppendingPathComponent:("Script Split RTFD at Delimiter" & space & (current application's NSProcessInfo's processInfo()'s globallyUniqueString())))
set {successCreateDir, theError} to current application's NSFileManager's defaultManager's createDirectoryAtURL:theTempDirectoryURL withIntermediateDirectories:false attributes:(missing value) |error|:(reference)
if theError ≠ missing value then error (theError's localizedDescription() as string)
return theTempDirectoryURL
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"createTempDirectory\"" message error_message as warning
error number -128
end try
end createTempDirectory
on writeAttributedStringToTempFile(theAttributedString, theRange, theTempDirectoryURL)
try
set thisAttributedString to (my regexReplaceAttributedString(((theAttributedString's attributedSubstringFromRange:theRange))'s mutableCopy(), "(^\\s+|\\s+$)", ""))
set thisAttributedString_Range to {location:0, |length|:thisAttributedString's |length|()}
if (thisAttributedString's containsAttachmentsInRange:thisAttributedString_Range) then
set thisFileWrapper to (thisAttributedString's RTFDFileWrapperFromRange:thisAttributedString_Range documentAttributes:{NSDocumentTypeDocumentAttribute:(current application's NSRTFDTextDocumentType)})
set thisTempURL to ((theTempDirectoryURL's URLByAppendingPathComponent:(current application's NSProcessInfo's processInfo()'s globallyUniqueString()))'s URLByAppendingPathExtension:"rtfd")
set {successWrite, theError} to (thisFileWrapper's writeToURL:thisTempURL options:(current application's NSFileWrapperWritingAtomic) originalContentsURL:(missing value) |error|:(reference))
else
set thisData to (thisAttributedString's RTFFromRange:(thisAttributedString_Range) documentAttributes:{NSDocumentTypeDocumentAttribute:(current application's NSRTFTextDocumentType)})
set thisTempURL to ((theTempDirectoryURL's URLByAppendingPathComponent:(current application's NSProcessInfo's processInfo()'s globallyUniqueString()))'s URLByAppendingPathExtension:"rtf")
set {successWrite, theError} to (thisData's writeToURL:thisTempURL options:(current application's NSDataWritingAtomic) |error|:(reference))
end if
if theError ≠ missing value then error (theError's localizedDescription() as string)
return {thisAttributedString, thisTempURL}
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"writeAttributedStringToTempFile\"" message error_message as warning
error number -128
end try
end writeAttributedStringToTempFile
on regexReplaceAttributedString(theMutableAttributedString, thePattern, theReplacementPattern) -- based on Shane Stanley's code, https://forum.latenightsw.com/t/read-and-write-rtf-files/1200/5
try
set {theRegex, theError} to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(reference)
if theRegex = missing value then error theError's localizedDescription() as text
set theMutableAttributedString_string to theMutableAttributedString's |string|()
set theMatches to (theRegex's matchesInString:theMutableAttributedString_string options:0 range:{0, theMutableAttributedString_string's |length|()}) as list
set theMatches to reverse of theMatches
repeat with thisMatch in theMatches
(theMutableAttributedString's replaceCharactersInRange:(thisMatch's range()) withString:theReplacementPattern)
end repeat
return theMutableAttributedString
on error error_message number error_number
activate
if the error_number is not -128 then display alert "Error: Handler \"regexReplaceAttributedString\"" message error_message as warning
error number -128
end try
end regexReplaceAttributedString