Hello
You might try the following script using rubycocoa, which will get rtf data from clipboard and put edited rtf data back to clipboard. In order to use it, copy source range in Numbers, run the script and paste result to destination range in Numbers.
E.g.,
Tested with Numbers v2.0.5 under OS X 10.6.8.
Under OS X 10.10, you need to manually install RubyCocoa 1.2.0 which supports Ruby 2.0 or later.
http://rubycocoa.sourceforge.net/
http://sourceforge.net/projects/rubycocoa/files/RubyCocoa/1.2.0/
and change ruby interpreter in script to:
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
require 'osx/cocoa'
include OSX
SUPERSCRIPT_FACTOR = 1.0
SUPERSCRIPT_NESTED = true
def indices(s, q)
# string s, q : source string, query string
# return array : indices of q in s
pp = []
tr = NSMakeRange(0, s.length)
while tr.length > 0
r = s.objc_send(
:rangeOfString, q,
:options, NSLiteralSearch,
:range, tr)
# only use r.location, for r.length can be invalid (e.g., <= 0).
# check if r.location is valid, for r.location can be invalid (e.g., < 0 or >= s.length)
break if r.location < 0 or r.location >= s.length # NSNotFound should work but not actually.
pp << r.location
tr = NSMakeRange(r.location + q.length, s.length - r.location - q.length)
end
pp
end
def blocks(pp, qq)
# array pp : array of integers sorted in ascending order (e.g. offsets of block start tag in text)
# array qq : list of integers sorted in ascending order (e.g. offsets of block end tag in text)
# return array : array representing block structure, such that -
# each element is an array whose first and last elements represent block start and end offsets respectively,
# while middle elements, if any, represent nested blocks.
#
# * Note
# A: atomic block = [p, q]; p = start offset, q = end offset
# G: general block (with any nested blocks) = [p] + [(G|A)*] + [q]; (G|A)* = any occurences of G or A
# Block structure = [(G|A)*];. array of any occurences of G or A
#
# e.g.,
# pp = [2, 9, 13, 21, 33]
# qq = [18, 26, 27, 31, 36]
# return = [[2, [9, [13, 18], [21, 26], 27], 31], [33, 36]]
ix, jx = pp.length, qq.length
rr = []
rr.push pp[0] if ix > 0
i, j = 1, 0
while j < jx
p, q = pp[i], qq[j]
if p and p < q
rr.push p
i += 1
else
a = [q]
1 while (r = rr.pop and a.unshift r and r.is_a? Array)
a = a.first unless r
rr.push a
j += 1
end
end
rr
end
#
# get rtf data from pboard
#
pboard = NSPasteboard.generalPasteboard
pbtype = pboard.availableTypeFromArray(['public.rtf'])
exit if pbtype == nil
rtf = pboard.dataForType(pbtype)
docattr = OCObject.new
mas = NSMutableAttributedString.alloc.objc_send(
:initWithRTF, rtf,
:documentAttributes, docattr)
#
# get block structure defined by [ and ]
#
s = mas.string
pp = indices(s, '[')
qq = indices(s, ']')
bb = blocks(pp, qq)
#
# apply superscript to detected blocks
#
bb.reverse.each do |b|
p, q = b[0], b[-1]
r = NSMakeRange(p, q - p + 1)
# reduce font size in r
if SUPERSCRIPT_FACTOR > 0.0 and SUPERSCRIPT_FACTOR < 1.0
tr = r.dup
while tr.length > 0
er = NSRange.new
attr = mas.objc_send(
:attribute, NSFontAttributeName,
:atIndex, tr.location,
:longestEffectiveRange, er,
:inRange, tr)
font = NSFont.objc_send(
:fontWithDescriptor, attr.fontDescriptor,
:size, attr.pointSize * SUPERSCRIPT_FACTOR)
mas.objc_send(
:addAttributes, {NSFontAttributeName => font},
:range, er)
tr = NSMakeRange(NSMaxRange(er), tr.length - er.length)
end
end
# superscript r
mas.superscriptRange(r)
# superscript nested ranges recursively
if SUPERSCRIPT_NESTED
u = b
while u = u.length > 2 ? u[1] : nil
i, j = u[0], u[-1]
mas.superscriptRange(NSMakeRange(i, j - i + 1))
end
end
# delete [ and ] in r
b.flatten.reverse.each do |k|
mas.deleteCharactersInRange(NSMakeRange(k, 1))
end
end
mas.fixAttributesInRange(NSMakeRange(0, mas.length))
#
# create rtf
#
rtf1 = mas.objc_send(
:RTFFromRange, NSMakeRange(0, mas.length),
:documentAttributes, docattr)
#
# put rtf data in pboard
#
pbtype = 'public.rtf'
pboard.objc_send(
:declareTypes, [pbtype],
:owner, nil)
pboard.objc_send(
:setData, rtf1,
:forType, pbtype)
exit
Notes.
• When I paste the resulting rtf data to TextEdit rtf document, everything is fine and as expected.
• However, when I paste the resulting rtf data to Numbers v2 (2.0.5) document or Pages v4 (4.0.5) document, some text may be incorrectly pasted. It appears these applications are doing something quite strange in pasting rtf data – for instance, a) some base text followed by superscript text may be pasted as all superscript text, b) superscript font size is arbitrarily reduced from the source size even if the size is already reduced in superscript. Especially a) is problematic and I have no workaround. E.g.,
abc[123]
is processed correctly and pasted correctly whereas
abc[1234]
is processed correctly but pasted wrongly as all superscript.
Some other anomalies.
It seems lengths of base and superscript text in paragraph are playing some role but after all, it is unpredictable and I'd classify it as bug of Numbers v2 and Pages v4. Later versions may vary.
• SUPERSCRIPT_FACTOR = 1.0, which generates superscript with the same font size as the base text. I set it to 1.0 because Numbers v2 and Pages v4 arbitrarily reduces the superscript font size in pasting rtf data.
• SUPERSCRIPT_NESTED = true, which generates nested superscripts although Numbers v2 and Pages v4 flattens it. You can see what it generates for
a[2[3]]
in TextEdit rtf document.
• It might not work with Numbers v3...
Good luck,
H