use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set theText to " <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
<a:clrScheme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"
name=\"BaseTheme\">
<a:dk1><a:srgbClr val=\"000000\"/></a:dk1>
<a:lt1><a:srgbClr val=\"FFFFFF\"/></a:lt1>
<a:dk2><a:srgbClr val=\"000000\"/></a:dk2>
<a:lt2><a:srgbClr val=\"FFFFFF\"/></a:lt2>
<a:accent1><a:srgbClr val=\"808080\"/></a:accent1>
<a:accent2><a:srgbClr val=\"808080\"/></a:accent2>
<a:accent3><a:srgbClr val=\"808080\"/></a:accent3>
<a:accent4><a:srgbClr val=\"808080\"/></a:accent4>
<a:accent5><a:srgbClr val=\"808080\"/></a:accent5>
<a:accent6><a:srgbClr val=\"808080\"/></a:accent6>
<a:hlink><a:srgbClr val=\"505050\"/></a:hlink>
<a:folHlink><a:srgbClr val=\"A0A0A0\"/></a:folHlink>
</a:clrScheme>"
set theDoc to current application's NSXMLDocument's alloc()'s initWithXMLString:theText options:(current application's NSXMLDocumentTidyXML) |error|:(missing value)
set theRoot to theDoc's rootElement()
theRoot's attributeForName:"name" -- return stringValue() or use setStringValue:
set rgbElement to ((theRoot's elementsForName:"a:dk1")'s firstObject()'s elementsForName:"a:srgbClr")'s firstObject()
set anAttribute to rgbElement's attributeForName:"val"
anAttribute's setStringValue:"FFFFFF"
Google will point you to several AppleScript handlers.
The option NSXMLDocumentTidyXML modifies the content so I chose NSXMLDocumentValidate.
I’m I right?
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use framework "AppKit"
set thePath to "~/Library/Application Support/Microsoft/Office/Modèles utilisateur/Mes thèmes/Theme Colors/aaaaaa.xml"
set thePath to current application's NSString's stringWithString:thePath
set thePath to thePath's stringByExpandingTildeInPath()
set theURL to current application's NSURL's fileURLWithPath:thePath
set theDoc to current application's NSXMLDocument's alloc()'s initWithContentsOfURL:theURL options:(current application's NSXMLDocumentValidate) |error|:(missing value)
set theRoot to theDoc's rootElement()
(theRoot's attributeForName:"name")'s setStringValue:"aaaaaa"
set anAttribute to ((theRoot's elementsForName:"a:accent4")'s firstObject()'s elementsForName:"a:srgbClr")'s firstObject()'s attributeForName:"val"
anAttribute's setStringValue:"FF0000"
set theData to theDoc's XMLData()
theData's writeToURL:theURL atomically:true
tell application id "com.microsoft.Powerpoint" -- Microsoft PowerPoint
activate
load theme color scheme (theme color scheme of theme of slide master of active presentation) file name (theURL as text)
end tell
About the RGB-HEX conversion, I thought there was an AppleScriptObjC method, but the only thing I found is not retuning the expected result:
current application's NSString's stringWithFormat_("#%02X%02X%02X", {theRed, theGreen, theBlue})
If I understand you well, there’s only the Vanilla AS syntax that’s possible?
convertRGBColorToHexValue({238, 115, 117})
on convertRGBColorToHexValue(theRGBValues)
set theHexList to {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}
set theHexValue to ""
repeat with a from 1 to count of theRGBValues
set theCurrentRGBValue to (item a of theRGBValues) -- div 256 for 65235 based values
if theCurrentRGBValue is 256 then set theCurrentRGBValue to 255
set theFirstItem to item ((theCurrentRGBValue div 16) + 1) of theHexList
set theSecondItem to item (((theCurrentRGBValue / 16 mod 1) * 16) + 1) of theHexList
set theHexValue to (theHexValue & theFirstItem & theSecondItem) as string
end repeat
return ("#" & theHexValue) as string
end convertRGBColorToHexValue
The problem with that approach is that you can’t really pass an integer because of bridging issues. You can work around it with a bit of hacky code:
((current application's NSString's stringWithFormat_("%04Xz%04Xz%04Xz", (item 1 of rgbList) / 256, (item 2 of rgbList) / 256, (item 3 of rgbList) / 256))'s stringByReplacingOccurrencesOfString:"57z" withString:"") as text
But it’s quite slow — it takes more than 0.01 seconds. Whereas the non-ASObjC method is more than 200 times faster. No contest, even if @NigelGarvey thinks we should be more patient See post below.
On my system, your AppleScriptObjC script takes not time and returns the right Hex number after a tiny modification. (I dont know why it adds the string “27z” and not “57z”. is it a typo?).
((current application's NSString's stringWithFormat_("%04Xz%04Xz%04Xz", (item 1 of rgbList) div 256, (item 2 of rgbList) div 256, (item 3 of rgbList) div 256))'s stringByReplacingOccurrencesOfString:"27z" withString:"") as text
Or perhaps a snap-together assembly from pre-existing parts, as if from a Lego bin ?
-- hexFromRGB :: (Int, Int, Int) -> String
on hexFromRGB(rgb)
"#" & intercalateString("", map(my showHex, rgb))
end hexFromRGB
-- TEST ------------------------------------------------------------------
on run
hexFromRGB({238, 115, 117})
--> "#EE7375"
end run
-- GENERIC FUNCTIONS ------------------------------------------------
-- intercalateString :: String -> [String] -> String
on intercalateString(s, xs)
set {dlm, text item delimiters} to {text item delimiters, s}
set str to xs as text
set text item delimiters to dlm
str
end intercalateString
-- intToDigit :: Int -> Char
on intToDigit(n)
if n ≥ 0 and n < 16 then
item (n + 1) of "0123456789ABCDEF"
else
"?"
end if
end intToDigit
-- showHex :: Int -> String
on showHex(n)
showIntAtBase(16, my intToDigit, n, "")
end showHex
-- showIntAtBase :: Int -> (Int -> Char) -> Int -> String -> String
on showIntAtBase(base, toChr, n, rs)
script go
property fChar : mReturn(toChr)'s |λ|
on |λ|(tpl, r)
set {n, d} to tpl
set r_ to fChar(d) & r
if n ≠0 then
|λ|(quotRem(n, base), r_)
else
r_
end if
end |λ|
end script
if base ≤ 1 then
"error: showIntAtBase applied to unsupported base"
else if n < 0 then
"error: showIntAtBase applied to negative number"
else
go's |λ|(quotRem(n, base), rs)
end if
end showIntAtBase
-- quotRem :: Int -> Int -> (Int, Int)
on quotRem(m, n)
{m div n, m mod n}
end quotRem
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn
-- unwords :: [String] -> String
on unwords(xs)
intercalateString(space, xs)
end unwords
@ComplexPoint
Even if I find your scripts very interesting, they are to much… complex for me.
I’m just a graphic designer whose developing some tools for my private use.
But I’m sure some others will get benefit from your participation!
@suzume
It’s not the Word document I want to modify.
It’s the color theme file located in the Applications Support system folder.
On the other hand, when a smallish set of general prefabricated units is available, snapping them together is possibly a bit simpler than starting from scratch. All we have to write is:
-- hexFromRGB :: (Int, Int, Int) -> String
on hexFromRGB(rgb)
"#" & intercalateString("", map(my showHex, rgb))
end hexFromRGB
I thought that out of deference to delicate sensibilities, I should avoid an intercalate built to be polymorphic (see below), but composition and fix time might have been saved if I had just stuck with reaching into the Lego bin …
-- intercalate :: [a] -> [[a]] -> [a]
-- intercalate :: String -> [String] -> String
on intercalate(sep, xs)
concat(intersperse(sep, xs))
end intercalate
-- concat :: [[a]] -> [a]
-- concat :: [String] -> String
on concat(xs)
if length of xs > 0 and class of (item 1 of xs) is string then
set acc to ""
else
set acc to {}
end if
repeat with i from 1 to length of xs
set acc to acc & item i of xs
end repeat
acc
end concat
-- intersperse(0, [1,2,3]) -> [1, 0, 2, 0, 3]
-- intersperse :: Char -> String -> String
-- intersperse :: a -> [a] -> [a]
on intersperse(sep, xs)
set lng to length of xs
if lng > 1 then
set acc to {item 1 of xs}
repeat with i from 2 to lng
set acc to acc & {sep, item i of xs}
end repeat
if class of xs is string then
concat(acc)
else
acc
end if
else
xs
end if
end intersperse
on hexFromRGB(rgbList)
set theResult to ""
repeat with aValue in rgbList
set theString to current application's NSString's stringWithFormat_("%04Xz", aValue)
set theResult to theResult & (theString's substringToIndex:2) as text
end repeat
return theResult
end hexFromRGB
on hexFromRGB(rgbList)
return text 1 thru 6 of ((current application's NSString's stringWithFormat_("%08X", (beginning of rgbList) div 256 * 65536 + (item 2 of rgbList) div 256 * 256 + (end of rgbList) div 256)) as text)
end hexFromRGB
BTW, Shane. Your handler doesn’t alway return the right results. eg.:
hexFromRGB({800, 255, 65535})
--> "32FFFF" (should be "0300FF")
It’s expecting values from 0-255. I’m not sure why I changed mid-thread — I think it was to match @ionah and @ComplexPoint’s versions that were based on 0-255 values. Although looking back there was a bit of jumping around.
Hex values are for sRGB colors.
If you pass a color described with 65536-based values, they can’t be less than 256 and they can’t be decimals. {800, 255, 65535} is not compatible with the sRGB color space.
use framework "Foundation"
use framework "AppKit"
use scripting additions
on hexFromRGB:rgbList
set theResult to ""
repeat with aValue in rgbList
if aValue > 255 then set aValue to (aValue div 256)
set theString to current application's NSString's stringWithFormat_("%04Xz", aValue)
set theResult to theResult & (theString's substringToIndex:2) as text
end repeat
return theResult
end hexFromRGB:
set result1 to my hexFromRGB:{65535, 29952, 29440}
set result2 to my hexFromRGB:{255, 117, 115}
result1 & " - " & result2