I want to get a list of the font styles (bold, italic) and their position/ranges from a “NSAttributedString”.
With “TextEdit.app” (NSTextStorage) i would use “attribute runs” to get the string segments and the font info.
How can i “extract” these attribute infos from a “NSAttributedString”?
It’s a bit of work. If you’re after a particular attribute, say just the font, here’s an example using some code I just posted elsewhere to build the attributed string:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions
-- classes, constants, and enums used
property NSMutableArray : a reference to current application's NSMutableArray
property NSForegroundColorAttributeName : a reference to current application's NSForegroundColorAttributeName
property NSFontAttributeName : a reference to current application's NSFontAttributeName
property NSUTF8StringEncoding : a reference to 4
property NSString : a reference to current application's NSString
property NSAttributedString : a reference to current application's NSAttributedString
set theText to "<font color=\"#FFF800\">We'll kick their ass.</font>
<font color=\"#00F400\"><i>You see?</i></font>"
set theText to NSString's stringWithString:theText
set theData to theText's dataUsingEncoding:NSUTF8StringEncoding
if theData = missing value then error (theError's localizedDescription() as text)
set theATS to NSAttributedString's alloc()'s initWithHTML:theData documentAttributes:(missing value)
if theATS is missing value then error "Could not interpret HTML"
set theLen to theATS's |length|()
set theIndex to 0
set theFonts to NSMutableArray's array()
set theStrings to NSMutableArray's array()
repeat
set {theValue, theRange} to theATS's |attribute|:NSFontAttributeName atIndex:theIndex longestEffectiveRange:(reference) inRange:{theIndex, theLen - theIndex}
set theIndex to current application's NSMaxRange(theRange)
theFonts's addObject:(theValue's fontName())
theStrings's addObject:(theATS's |string|()'s substringWithRange:theRange)
if theIndex ≥ theLen then exit repeat
end repeat
If you’re after multiple attributes, you use a similar approach with the method -attributesAtIndex:longestEffectiveRange:inRange:. That will break the string whenever any attribute changes, and will return a dictionary of all the attributes. (The strings here should match what you see for TextEdit’s attribute runs.)
Here’s my first working version. Code and performance is far from good but at least it is working.
As i turned out i only need to retrieve certain attributes (html: b, i, sub, sup, u) as well as their combinations.
Source is always the pasteboard with RTF and/or HTML.
I still need some time to get deeper into ASObjC to avoid the “switching” and hopefully speed up the script.
Thanks again!
run script getStyleRanges
script getStyleRanges
use framework "Foundation"
use framework "AppKit"
set thePB to current application's NSPasteboard's generalPasteboard()
set thePBFormats to thePB's pasteboardItems()'s firstObject()'s |types|() as list
if "public.rtf" is in thePBFormats then
set theData to thePB's dataForType:(current application's NSPasteboardTypeRTF)
set theATS to current application's NSAttributedString's alloc()'s initWithRTF:theData documentAttributes:(missing value)
else if "public.html" is in thePBFormats then
set theData to thePB's dataForType:(current application's NSPasteboardTypeHTML)
set theATS to current application's NSAttributedString's alloc()'s initWithHTML:theData documentAttributes:(missing value)
else
return {}
end if
if theATS is missing value then return {}
set theLen to theATS's |length|()
set theIndex to 0
set theRanges to {}
set theFontStyles to {b:{"Bold", "BoldMT", "Bd"}, i:{"Italic", "ItalicMT", "It", "Oblique", "Slanted"}}
repeat
set theStyleTags to {}
set {theValue, theRange} to theATS's attributesAtIndex:theIndex longestEffectiveRange:(reference) inRange:{theIndex, theLen - theIndex}
set theIndex to current application's NSMaxRange(theRange)
set theFontName to theValue's NSFont's fontName
if (count words of (theFontName as text)) is 2 then
repeat with i in b of theFontStyles
set aFontStyle to (current application's NSString's stringWithString:i)
if (theFontName's containsString:aFontStyle) then
copy "/b" to end of theStyleTags
exit repeat
end if
end repeat
repeat with i in i of theFontStyles
set aFontStyle to (current application's NSString's stringWithString:i)
if (theFontName's containsString:aFontStyle) then
copy "/i" to end of theStyleTags
exit repeat
end if
end repeat
end if
try
set theSuperScript to theValue's NSSuperScript as integer
if theSuperScript > 0 then
copy "/sup" to end of theStyleTags
else if theSuperScript < 0 then
copy "/sub" to end of theStyleTags
end if
end try
try
set theUnderline to theValue's NSUnderline as integer
if theUnderline = 1 then copy "/u" to end of theStyleTags
end try
if theStyleTags is not {} then copy {theStyleTags as text, theRange} to end of theRanges
if theIndex ≥ theLen then exit repeat
end repeat
return {theText:theATS's |string|() as text, theLen:theLen, theRanges:theRanges}
end script