Exporting a Plist Dict as a string

Hello all, didn’t get a response to the last topic so I have a more specific query that GoogleFu has not resolved.

I need to basically extract all user text replacements from ~/Library/Preferences/.GlobalPreferences.plist

I can use PlistBuddy, defaults, etc. to extract them, but they don’t work well for the “dict” that the replacements are saved in.

Applescript seems to not support plist reading the hierarchy this file is in very well.

The format the section I need to edit is:

<key>NSUserReplacementItems</key>
	<array>
		<dict>
			<key>on</key>
			<integer>1</integer>
			<key>replace</key>
			<string>(c)</string>
			<key>with</key>
			<string>©</string>
		</dict>
</array>

Basically, I need to make an array containing plaintext strings, the first containing all keys for the string “replace” and then a matching array containing the replacements “with”

I need to be able to read these without knowing the set amounts the plist contains because it can change easily.

Hi,

AppleScript does support plist reading. System Events contains a Property List Suite
which provides classes to parse property lists quite easy.

Try this

set globalUserPreferencesFile to (path to preferences folder as text) & ".GlobalPreferences.plist"
set plainTextList to {}
tell application "System Events"
	set plistFile to property list file globalUserPreferencesFile
	set NSUserReplacementItems to property list item "NSUserReplacementItems" of contents of plistFile
	repeat with anItem in property list items of NSUserReplacementItems
		set itemTextRepresentation to "on: "
		set itemValue to value of anItem
		try
			set onValue to itemValue's |on|
			set itemTextRepresentation to itemTextRepresentation & onValue & tab
		on error
			set itemTextRepresentation to itemTextRepresentation & "0" & tab
		end try
		set itemTextRepresentation to itemTextRepresentation & "replace: " & itemValue's replace & tab
		set itemTextRepresentation to itemTextRepresentation & "with: " & itemValue's |with|
		set end of plainTextList to itemTextRepresentation
	end repeat
end tell
set {TID, text item delimiters} to {text item delimiters, return}
set plainTextList to plainTextList as text
set text item delimiters to TID
plainTextList

You an use property list items:

set replaceList to {}
set withList to {}

set theFile to (path to preferences folder from user domain) & ".GlobalPreferences.plist" as string
tell application "System Events"
	set PLRoot to contents of property list file theFile
	if "NSUserReplacementItems" is in name of property list items of PLRoot then
		tell property list item "NSUserReplacementItems" of PLRoot
			repeat with i from 1 to count (property list items)
				set replacementItem to value of property list item i
				set end of replaceList to replace of replacementItem
				set end of withList to |with| of replacementItem
			end repeat
		end tell
	end if
end tell
return {replaceList, withList}

or when you need to consider the on or off state (check box state in system preferences). The code above returns all replacements while the code below only returns if property ‘on’ is 1.

set replaceList to {}
set withList to {}

set theFile to (path to preferences folder from user domain) & ".GlobalPreferences.plist" as string
tell application "System Events"
	set PLRoot to contents of property list file theFile
	if "NSUserReplacementItems" is in name of property list items of PLRoot then
		tell property list item "NSUserReplacementItems" of PLRoot
			repeat with i from 1 to count (property list items)
				set replacementItem to (value of property list item i) & {replace:"", |on|:0, |with|:""} --default values
				if (|on| of replacementItem) as boolean then
					set end of replaceList to replace of replacementItem
					set end of withList to |with| of replacementItem
				end if
			end repeat
		end tell
	end if
end tell
return {replaceList, withList}

edit: Stefan’s post didn’t exists when I was creating this post. Also updated the script more to the wishes of TS

Thank you both so much! Perfect solutions :smiley:

I have just one last query, I am nearly finished with the script but It’s currently too easy to break:

So far it extracts the plist data into the arrays thanks to your scripts.

I have it starting up system preferences, going into the text replacement section and beginning to add the text replacements, but it always breaks after the first one…

Do you have a better method for adding text replacements instead of the following:


set shortcutList to {}
set withList to {}

set theFile to "/Volumes/MATSTARES/ENUserData/.GlobalPreferences.plist" as string

tell application "System Events"
	set PLRoot to contents of property list file theFile
	if "NSUserReplacementItems" is in name of property list items of PLRoot then
		tell property list item "NSUserReplacementItems" of PLRoot
			set totalReplacements to count (property list items)
			repeat with i from 1 to count (property list items)
				set replacementItem to (value of property list item i) & {replace:"", |on|:0, |with|:""} --default values
				if (|on| of replacementItem) as boolean then
					set end of shortcutList to replace of replacementItem
					set end of withList to |with| of replacementItem
				end if
			end repeat
		end tell
		
		tell process "System Preferences"
			delay 2
			click menu item "Language & Text" of menu "View" of menu bar 1
			tell window "Language & Text"
				delay 1
				click radio button "Text" of tab group 1
				delay 1
				
				repeat with i from 1 to totalReplacements
					click button 1 of tab group 1
					
					set the clipboard to item i of shortcutList
					keystroke "v" using {command down}
					
					keystroke tab
					
					set the clipboard to item i of withList
					keystroke "v" using {command down}
					
				end repeat
				
			end tell
		end tell
		
	end if
end tell

Hello.

This won’t help much, but a little:

Go to your preferences folder, and open the bundle of the preference pane you want.

Drill down until you find the property list file.

Open the property list file.

Find the anchor name to use, this anchorname works with any localization, as far a s I know.

Start Scripting:

Use system events to reveal the anchor of the preference pane.

Then activate the preference pane by system events.

And then you can continue the UI Scripting part, which should be less than before.

Thankyou for the response, but there isn’t one for the text replacements, they’re saved in .GlobalPreferences.plist, which is only read once (on login), so I need to GUI script them in because they need to be added without needing to log out and back in.

I made an handler:

setGlobalReplacementItem(" /0 ", " ø ", false)

on setGlobalReplacementItem(_replace, _with, _state)
	set replacementItem to {replace:_replace, |on|:_state as integer, |with|:_with}
	set theFile to (path to preferences folder from user domain) & ".GlobalPreferences.plist" as string
	tell application "System Events"
		set PLRoot to contents of property list file theFile
		if "NSUserReplacementItems" is not in name of property list items of PLRoot then
			set r to make new property list item at end of PLRoot with properties {kind:list, name:"NSUserReplacementItems", value:{}}
		end if
		tell property list item "NSUserReplacementItems" of PLRoot
			repeat with i from 1 to count (property list items)
				set currentItem to (value of property list item i) & {|on|:0} --if no on key then set to 0
				if replace of currentItem is equal to replace of replacementItem then
					set value of property list item i to replacementItem
					return --there is no reason to continue
				end if
			end repeat
			make new property list item at end of it with properties {kind:record, value:replacementItem}
		end tell
	end tell
end setGlobalReplacementItem

No, it is not for the text replacement, just for making the UI Scripting part smaller.

I’m sorry that I really can’t think of any other way, than the one you are using, to make the settings take effect promptly.

In all honesty, I’m not sure what to do with this :\

It checks if there is an entry NSUserReplacementItems in the plist file, if not create one. Then it checks if the given replace string is already in there, if it’s in there then replace it with the new value. At last, if the replace string can’t be found, add it to end of NSUserReplacementItems property.

Unfortunately, like McUsr mentioned, editing this file outside System Preferences will not take immediate effect. The system needs to reload the file first so there are three things you can do:

  1. give the system a restart command

  2. give the user a notification that the changed will take affect after relogin

  3. run the script, start system preferences, show the localization pane, click ‘Show All’ button and close system preferences.

:lol:

Now that is something that is quite easy to do with UI Scripting

Just an idea:

do shell script "defaults write .GlobalPreferences NSUserReplacementItems \"`defaults read /Volumes/MATSTARES/ENUserData/.GlobalPreferences NSUserReplacementItems`\"" 

This will copy the NSUserReplacementItems from MATSTARES volume to you user’s preference folder.

I don’t know what the volume MATSTARES is (USB, Optical, AFP, SMB, NFS, FUSE etc…), and can’t give a good example.

If you can download or mount the volume at startup you can use the command (for a mounted volume) and save it as arguments in an start up item. This way if you reboot the system it has always the latest version of your server’s globalPreferences.

Hello!

I picked the name of the anchor out of the bundle for the preference pane for the anchor, in the /System/Library/Preference/Panes/Localization.prefPane/Contents/Resourses/English.lproj/InfoPlist.strings

:slight_smile:


tell application "System Preferences"
	reveal anchor "Formats" of pane "com.apple.Localization"
	activate
end tell

I very very much appreciate the help you guys have provided, it’s simply amazing how helpful this community is; You guys are epic!

Just got one last favor to ask: I’ve tried to use your codes and have it backing up the plist data and have a loop ready to go for inputting it, but I can’t target the inputs properly :frowning: I can have it use the auto-focus and assume it’s targetted but that’s really prone to breaking if the user makes another window active mid-script.

How can I target these elements specifically?

  1. The “+” button; I can click it by using
click button 1 of tab group 1
  1. Input the plist info into the text field “Replace” (the highlighted one), and have it input it regardless of active window (rather than assuming it’s active, which is the issue I have currently)

I’ve tried with similar variants to this:

set value of text field 1 of table row 66 of table 0 to "hello world"

TextField 1 of row 1 of table 1 of scroll area 1 of tab group 1
  1. Then input the other info into the right hand text field; “With”

  2. And loop between adding them all one after another (And if you’re feeling generous, ensuring there are no duplicates before looping)

Hello!

I am not totally sure of this, but I expect the abbreviations to be same as the one that can be defined by cocoa text bindings, look at Create custom keybindings - Mac OS X Hints and see if that is usable.

And, if you don’t have the developer tools, I think it is time for you to get them. In there is a utility named UIELementInspector.app. :slight_smile: I think the usage of it is explained at macosxautomation.com.

Edit
There are also some tools and tricks to be found here. For extracting properties of elements in general, and some targetting UI Elements specially.

MacScripter / Coerce GUI scripting information into string?

Kai Edwards posted a nice article about extracting properties in Unscripted.

Edit++ I guess you use the UIElementor.app :slight_smile: If the focusing of the window is a problem, couldn’t you set its status to focused, between each step. You should have a very fast user if he manages to interfere then! :slight_smile:

Hello.

Here is the good link for the text-bindings : Cocoa Text System.

My idea is, that it is better to add them programatically, and then maybe just invoking the preference pane, and then “show all” to make them take effect, in order to avoid the UI scripting almost all together.

Thank you so much for that script! I could kiss you!!! MWAH!

I didn’t write it, and it is kind of usable, isn’t it? :wink: