Script: Split RTF(D) at Font Sizes

This script splits RTF(D) records at Font Sizes.

It optionally groups the resulting records according to their Font Size level.

It also optionally changes the font and font size.

Idea

While it’s not possible to get RTF headings directly it is possible to

  • analyze the text

  • assume that the font size that’s most used is the body text’s font size

  • assume that everything that’s larger than the body text’s font size * ratio X is a heading

This way it’s possible to ask the user at which ratio X a RTF should be split. Thanks for that chrillek!

Setup

You can change every property.

  • property joinHeadings: Join headings if there’s no body text between them

  • property dontCreateHeadingOnlyRecord: Don’t create a record if it would only contain a heading

  • property changeFont: If true 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 to 0 then DEVONthink’s rich text font size is used (see Preferences > Edit )

  • property theFontName: The desired font’s family name, e.g. "Lucida Grande"
    If set to "" then DEVONthink’s rich text font is used (see Preferences > 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)

  • property createGroups: Group records according to their heading level.
    Note: This may produce unwanted results if used with badly structured headings.
    E.g. RTFs that were created by converting PDFs sometimes work fine, however often it’s necessary to remove the first “page” from the RTF before groups are created correctly.

  • property createGroups_dontGroupLastLevel: Don’t create a group for the last heading level.
    E.g. heading “1.1.1” goes into group “1.1”

  • property createScriptSettingsGroup: Creates additional group whose name contains some of the script’s settings

For easier usage save different script versions, one for each settings combination you intend to use.

Usage

  • Select some RTF(D) record

  • Run script

  • You’ll be asked at the which ratio the record should be split.
    The default is 1.3 which often works fine.

  • If the result isn’t the desired one try again with another ratio

It is, of course, also possible to first manually check the body text’s font size and the font size of the lowest heading level you’d like to split at. Then divide the heading thru the body text font size.

Help …

  • … it doesn’t group correctly! Grouping depends on the order of headings. If groups aren’t created correctly check the order of the first headings in your RTF. You’ll probably find that there’s something mixed up. That’s often the case with RTFs that were created by converting PDFs or by clipping from the web. Removing mixed up headings should be sufficient to fix incorrect grouping.

  • … it doesn’t split correctly! If this script doesn’t produce the desired result (even after several runs) please let me know. You can always use Script: Split RTF(D) at Delimiter.

-- Split RTF(D) at Font Sizes

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property joinHeadings : true -- join headings if there's no text between them.
property dontCreateHeadingOnlyRecord : true -- don't create a record if it would only contain a heading

property changeFont : false -- 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" 

property createGroups : true -- attempt to group records according to their heading level. this may produce unwanted results if used with badly structured headings
property createGroups_dontGroupLastLevel : true -- don't group last heading level. e.g. heading "1.1.1" goes into group "1.1"
property createSmartGroup : false

property createScriptSettingsGroup : true -- create additional group whose name contains some of the script's settings

global theRatio_Heading, theProgressSteps

tell application id "DNtp"
	try
		set theRecords to selected records whose type = rtfd or type = rtf
		if theRecords = {} then error "Please select some RTF(D) record"
		
		set theChooseFromListItems to {"1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "2.0"}
		set theChoice to choose from list theChooseFromListItems with prompt "Split at Font Sizes ≥ Body Text * " default items (item 3 of theChooseFromListItems) with title ""
		if theChoice is false then return
		set theRatio_Heading to (item 1 of theChoice) as real
		
		repeat with thisRecord in theRecords
			set thisRecord_Path to path of thisRecord
			set thisRecord_Name to name without extension of thisRecord
			my splitRTFDatFontSizes(thisRecord_Path, thisRecord_Name, thisRecord)
		end repeat
		
	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 splitRTFDatFontSizes(theRecord_Path, theRecord_Name, theRecord)
	try
		if changeFont then
			set theProgressSteps to 5
		else
			set theProgressSteps to 3
		end if
		
		set theAttributedString to my readAttributedString(theRecord_Path)
		set theAttributedString_Length to theAttributedString's |length|()
		if theAttributedString_Length = 0 then return
		
		---------------------------------------------------------- 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 theCharacterSet to (current application's NSCharacterSet's alphanumericCharacterSet())'s invertedSet()
		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()), |Substring_alphanumeric|:(((theAttributedString's attributedSubstringFromRange:thisFontRange)'s |string|())'s stringByTrimmingCharactersInSet:theCharacterSet)}
			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
		
		------------------------------------------------------------ Get Headings --------------------------------------------------------------------
		
		set theFontSize_HeadingMin to theFontSize_Body * theRatio_Heading
		set theFontSizes_Headings to (theFontSizes's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self => " & (current application's NSNumber's numberWithDouble:theFontSize_HeadingMin))))
		set theFontSizes_Headings_withoutSingles to my getFontSizesHeadingsWithoutSingles(theFontSizes_Headings, theAttributesArray)
		set theHeadingsArray to (theAttributesArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontSize IN " & "{" & (theFontSizes_Headings_withoutSingles's componentsJoinedByString:",") & "}" & " AND " & "!self.Substring_alphanumeric = ''")))
		if (theHeadingsArray's |count|()) = 0 then
			tell application id "DNtp" to hide progress indicator
			return
		end if
		
		------------------------------------------------------- Create Substring Ranges -------------------------------------------------------------
		
		tell application id "DNtp" to step progress indicator
		set theBodyTextArray to (theAttributesArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontSize < " & (current application's NSNumber's numberWithDouble:theFontSize_HeadingMin))))
		if joinHeadings then set theSortDescriptor to (current application's NSSortDescriptor's sortDescriptorWithKey:"FontSize" ascending:false selector:"compare:")
		set i to 0
		set theCount to (theHeadingsArray's |count|()) - 1
		set theSubstringRanges to current application's NSMutableArray's new()
		repeat while i ≤ theCount
			set thisItem to (theHeadingsArray's objectAtIndex:i)
			set thisLocation to (thisItem's valueForKeyPath:"FontRange.location") as integer
			set thisLength to (thisItem's valueForKeyPath:"FontRange.length") as integer
			if i = 0 and thisLocation ≠ 0 then
				set thisLength to thisLength + thisLocation
				set thisLocation to 0
			end if
			set thisSubstring to missing value
			set createRecord to true
			if i < theCount then
				if joinHeadings then
					set thisRangeMax to thisLocation + thisLength
					set thisArray to current application's NSMutableArray's arrayWithArray:{thisItem}
					set joinedHeadings to missing value
					repeat with j from (i + 1) to theCount
						set thisNextLocation to (((theHeadingsArray's objectAtIndex:(j)))'s valueForKeyPath:"FontRange.location") as integer
						set thisBodyTextArray_filtered to (theBodyTextArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontRange.location BETWEEN {" & thisRangeMax & "," & (thisNextLocation - 1) & "}" & " AND " & "!self.Substring_alphanumeric = ''")))
						if (thisBodyTextArray_filtered's |count|()) > 0 then
							exit repeat
						else
							(thisArray's addObject:(theHeadingsArray's objectAtIndex:(j)))
							set joinedHeadings to true
							set i to i + 1
						end if
					end repeat
					if joinedHeadings = true then
						set thisSubstring to (thisArray's sortedArrayUsingDescriptors:{theSortDescriptor})'s firstObject()'s valueForKey:"Substring_alphanumeric"
					end if
				else
					set thisNextLocation to (((theHeadingsArray's objectAtIndex:(i + 1)))'s valueForKeyPath:"FontRange.location") as integer
					if dontCreateHeadingOnlyRecord then
						set thisRangeMax to thisLocation + thisLength
						set thisBodyTextArray_filtered to (theBodyTextArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontRange.location BETWEEN {" & thisRangeMax & "," & (thisNextLocation - 1) & "}" & " AND " & "!self.Substring_alphanumeric = ''")))
						if (thisBodyTextArray_filtered's |count|()) = 0 then
							if not (theAttributedString's containsAttachmentsInRange:{location:thisLocation, |length|:(thisNextLocation - thisLocation)}) then
								set createRecord to false
							end if
						end if
					end if
				end if
				set theLength to thisNextLocation - thisLocation
			else if i = theCount then
				set theLength to theAttributedString_Length - thisLocation
			end if
			set thisRange to {location:thisLocation, |length|:theLength}
			if thisSubstring = missing value then set thisSubstring to thisItem's valueForKey:"Substring_alphanumeric"
			(theSubstringRanges's addObject:{|Range|:thisRange, FontSize:(thisItem's valueForKey:"FontSize"), FirstLine:(paragraph 1 of (thisSubstring as string)), createRecord:createRecord})
			set i to i + 1
		end repeat
		
		------------------------------------------------------------- Change Font ----------------------------------------------------------------------
		
		if changeFont then
			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
		
		------------------------------------------------------ Write Temp Files & Import ---------------------------------------------------------------
		
		tell application id "DNtp" to show progress indicator "Splitting... " & theRecord_Name steps theProgressSteps with cancel button
		tell application id "DNtp" to step progress indicator
		
		set theTempDirectoryURL to my createTempDirectory()
		
		if createGroups then
			if createGroups_dontGroupLastLevel then
				if ((theFontSizes_Headings_withoutSingles's |count|()) > 1) then
					set theFontSize_GroupingMin to (theFontSizes_Headings_withoutSingles's objectAtIndex:(((theFontSizes_Headings_withoutSingles's |count|()) as integer) - 2)) as real
					set theGroupLevelDictionary to current application's NSMutableDictionary's new()
				else
					set createGroups to false
				end if
			else
				set theFontSize_GroupingMin to (theFontSizes_Headings_withoutSingles's objectAtIndex:(((theFontSizes_Headings_withoutSingles's |count|()) as integer) - 1)) as real
				set theGroupLevelDictionary to current application's NSMutableDictionary's new()
			end if
		end if
		
		set theMainGroup_Name to theRecord_Name
		if createScriptSettingsGroup then
			set theNumberFormatter to current application's NSNumberFormatter's new()
			theNumberFormatter's setMaximumFractionDigits:2
			theNumberFormatter's setMinimumFractionDigits:2
			set theMainGroup_Name to theMainGroup_Name & " [Split at Font Sizes ≥ " & ((theNumberFormatter's stringFromNumber:theFontSize_HeadingMin) as string) & " (Ratio " & theRatio_Heading & ") | joinHeadings = " & joinHeadings & " | dontCreateHeadingOnlyRecord = " & dontCreateHeadingOnlyRecord
			if createGroups then set theMainGroup_Name to theMainGroup_Name & " | dontGroupLastLevel = " & createGroups_dontGroupLastLevel
			set theMainGroup_Name to theMainGroup_Name & "]"
		end if
		
		tell application id "DNtp"
			if createScriptSettingsGroup then
				set theContainerGroup to create record with {name:theMainGroup_Name, type:group} in location group of theRecord
				set theContainerGroup_UUID to uuid of theContainerGroup
				set {theMainGroup, theMainGroup_UUID} to my createGroupInGroupWithUUID(theContainerGroup_UUID, theRecord_Name)
			else
				set theMainGroup to create record with {name:theMainGroup_Name, type:group} in parent 1 of theRecord
				set theMainGroup_UUID to uuid of theMainGroup
			end if
			if createGroups and createSmartGroup then create record with {type:smart group, search predicates:"{any: kind:rtf kind:group}", name:"{any: kind:rtf kind:group} in: " & theRecord_Name, search group:theMainGroup} in theMainGroup
		end tell
		
		repeat with i from 0 to ((theSubstringRanges's |count|()) - 1)
			set thisItem to (theSubstringRanges's objectAtIndex:i)
			set thisName to (thisItem's valueForKey:"FirstLine") as string
			if createGroups then
				set thisFontSize to (thisItem's valueForKey:"FontSize")
				set thisHeadingLevel to ((theFontSizes_Headings_withoutSingles's indexOfObject:thisFontSize) as integer) + 1
				if thisHeadingLevel = 1 then
					set {thisGroup, thisGroup_UUID} to my createGroupInGroupWithUUID(theMainGroup_UUID, thisName)
					(theGroupLevelDictionary's removeObjectsForKeys:(theGroupLevelDictionary's allKeys()))
					(theGroupLevelDictionary's setObject:{GroupLevel:thisHeadingLevel, UUID:thisGroup_UUID, GroupName:thisName} forKey:(thisHeadingLevel as string))
				else if thisHeadingLevel > 1 then
					set thisGroup_UUID to (theGroupLevelDictionary's valueForKeyPath:(((thisHeadingLevel - 1) as string) & ".UUID"))
					if thisGroup_UUID = missing value then
						repeat with j from 1 to thisHeadingLevel
							set thisGroup_UUID to (theGroupLevelDictionary's valueForKeyPath:(((thisHeadingLevel - j) as string) & ".UUID"))
							if thisGroup_UUID ≠ missing value then exit repeat
						end repeat
					end if
					if thisGroup_UUID = missing value then set thisGroup_UUID to theMainGroup_UUID
					set thisGroup_UUID to thisGroup_UUID as string
					if thisFontSize as real ≥ theFontSize_GroupingMin then
						set {thisGroup, thisGroup_UUID} to my createGroupInGroupWithUUID(thisGroup_UUID, thisName)
						(theGroupLevelDictionary's setObject:{GroupLevel:thisHeadingLevel, UUID:thisGroup_UUID, GroupName:thisName} forKey:(thisHeadingLevel as string))
					else
						tell application id "DNtp" to set thisGroup to get record with uuid thisGroup_UUID
					end if
				end if
			else
				set thisGroup to theMainGroup
			end if
			if ((thisItem's valueForKey:"createRecord") as boolean) then
				set {thisAttributedString, thisTempURL} to my writeAttributedStringToTempFile(theAttributedString, (thisItem's valueForKey:"Range"), theTempDirectoryURL)
				set thisTempPath to (thisTempURL's |path|()) as string
				tell application id "DNtp" to import thisTempPath name thisName to thisGroup
			end if
		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 \"splitRTFDatFontSizes\"" 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 splitRTFDatFontSizes

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 getFontSizesHeadingsWithoutSingles(theFontSizes_Headings, theAttributesArray)
	try
		set theArray to current application's NSMutableArray's new()
		repeat with i from 0 to ((theFontSizes_Headings's |count|()) - 1)
			set thisFontSize to (theFontSizes_Headings's objectAtIndex:i)
			set thisAttributesArray_filtered to (theAttributesArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:("self.FontSize = " & thisFontSize)))
			(theArray's addObject:{FontSize:thisFontSize, FirstLocation:(((thisAttributesArray_filtered's firstObject())'s valueForKeyPath:"FontRange.location")), Occurrences:(thisAttributesArray_filtered's |count|())})
		end repeat
		set theArray_sortedByFirstLocation to (theArray's sortedArrayUsingDescriptors:{(current application's NSSortDescriptor's sortDescriptorWithKey:"FirstLocation" ascending:true selector:"compare:")})
		set theRemoveArray to current application's NSMutableArray's new()
		repeat with i from 0 to ((theArray_sortedByFirstLocation's |count|()) - 1)
			set thisItem to (theArray_sortedByFirstLocation's objectAtIndex:i)
			set thisFontSize to (thisItem's valueForKey:"FontSize") as real
			set thisFontSize_Occurrences to (thisItem's valueForKey:"Occurrences") as integer
			if thisFontSize_Occurrences = 1 then
				repeat with j from (i + 1) to ((theArray_sortedByFirstLocation's |count|()) - 1)
					set thatFontSize to ((theArray_sortedByFirstLocation's objectAtIndex:j)'s valueForKey:"FontSize") as real
					if thisFontSize < thatFontSize then
						(theRemoveArray's addObject:thisItem)
						exit repeat
					end if
				end repeat
			end if
		end repeat
		set theArray_sortedByFontSize to (theArray's sortedArrayUsingDescriptors:{(current application's NSSortDescriptor's sortDescriptorWithKey:"FontSize" ascending:false selector:"compare:")})'s mutableCopy()
		theArray_sortedByFontSize's removeObjectsInArray:theRemoveArray
		set theFontSizes_Headings_withoutSingles to theArray_sortedByFontSize's valueForKey:"FontSize"
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"getFontSizesHeadingsWithoutSingles\"" message error_message as warning
		error number -128
	end try
end getFontSizesHeadingsWithoutSingles

on createTempDirectory()
	try
		set theTempDirectoryURL to current application's |NSURL|'s fileURLWithPath:((current application's NSTemporaryDirectory())'s stringByAppendingPathComponent:("Script Split RTFD at Font Sizes" & 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 (theAttributedString's attributedSubstringFromRange:theRange)
		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 createGroupInGroupWithUUID(theParentGroup_UUID, theNewGroup_Name)
	try
		tell application id "DNtp"
			set theParentGroup to get record with uuid theParentGroup_UUID
			set theNewGroup to create record with {name:theNewGroup_Name, type:group} in theParentGroup
			set theNewGroup_UUID to uuid of theNewGroup
			return {theNewGroup, theNewGroup_UUID}
		end tell
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"createGroupInGroupWithUUID\"" message error_message as warning
		error number -128
	end try
end createGroupInGroupWithUUID

3 Likes

My mind boggles. Chapeau!

2 Likes