I need to read and edit part of a plist file, it looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Bookstore</key>
<dict>
<key>Public</key>
<dict>
<key>Books</key>
<array>
<dict>
<key>Name</key>
<string>The Old Man and the Sea</string>
<key>Author</key>
<string>Hemingway</string>
</dict>
<dict>
<key>Name</key>
<string>A Brief History of Time</string>
<key>Author</key>
<string>Stephen Hawkin</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
What I've done so far:
require 'nokogiri'
doc = doc = Nokogiri::XML(File.open("books.plist"))
doc.xpath("//dict[key=\"Bookstore\"]/dict[key=\"Public\"]/dict[key=\"Books\"]")
If I want to get an array of hashes (in Ruby) of elements under "Book", what should I do next?
Thanks!
I think you can expand your selection to grab exactly the elements you need:
require 'nokogiri'
doc = Nokogiri::XML(File.open("books.plist"))
books = doc.xpath("//dict[key='Bookstore']/dict[key='Public']/dict[key='Books']/array/dict")
Then you can convert this array of book elements into the shape you want by sub-selecting, or traversing, but sub-selecting might look like this:
final = books.map do |book|
keys = book.xpath("key/text()").map(&:to_s)
values = book.xpath("string/text()").map(&:to_s)
Hash[keys.zip(values)]
end
You can use Ruby gems like plist_lite
To answer your question:
If I want to get an array of hashes (in Ruby) of elements under "Book", what should I do next?
ruby -rplist_lite -e 'pp PlistLite.load($stdin.read)["Bookstore"]["Public"]["Books"]' <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Bookstore</key>
<dict>
<key>Public</key>
<dict>
<key>Books</key>
<array>
<dict>
<key>Name</key>
<string>The Old Man and the Sea</string>
<key>Author</key>
<string>Hemingway</string>
</dict>
<dict>
<key>Name</key>
<string>A Brief History of Time</string>
<key>Author</key>
<string>Stephen Hawkin</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
EOS
output
[{"Name"=>"The Old Man and the Sea", "Author"=>"Hemingway"},
{"Name"=>"A Brief History of Time", "Author"=>"Stephen Hawkin"}]
If you don't want to require extra dependency, you can still refer to it's source code, plist_lite uses nokogiri for parsing as well:
https://github.com/tonytonyjan/plist_lite/blob/master/lib/plist_lite.rb#L37-L57
To update the plist file, use PlistLite#dump:
obj = PlistLite.load(IO.read('test.plist'))
# modify obj...
IO.write('test.plist', PlistLite.dump(obj))
Related
I'm new to Ruby and need to process some plist for a book store, say if I have a plist of books,
<plist>
<array>
<dict>
<key>Name</key>
<string>An Old Man and the Sea</string>
<key>Available</key>
<true/>
</dict>
<dict>
<key>Name</key>
<string>The Hitchhiker's Guide To Galaxy</string>
<key>Available</key>
<false/>
</dict>
</array>
</plist>
I need to switch the availability of books, and I have read the plist and parsed the array of books into books:
books.map do |book|
book.xpath("key/text()") # e.g. ["Name", "Available"]
book.xpath("string/text()") # e.g. ["An Old Man and the Sea"]
end
Questions:
How do I read the value <true/> and <false/>?
How do I update the value and save the plist?
Thanks!
If you don't mind using a gem, there is plist_lite.
ruby -r plist_lite -e 'p PlistLite.load($stdin.read)' <<EOS
<plist>
<array>
<dict>
<key>Name</key>
<string>An Old Man and the Sea</string>
<key>Available</key>
<true/>
</dict>
<dict>
<key>Name</key>
<string>The Hitchhiker's Guide To Galaxy</string>
<key>Available</key>
<false/>
</dict>
</array>
</plist>
EOS
output:
[{"Name"=>"An Old Man and the Sea", "Available"=>true},
{"Name"=>"The Hitchhiker's Guide To Galaxy", "Available"=>false}]
If you want to do it yourself with nokogiri, you can refer to plist_lite's source code here, it also uses nokogiri:
https://github.com/tonytonyjan/plist_lite/blob/2de0506/lib/plist_lite.rb#L37-L57
To answer your question:
How do I read the value <true/> and <false/>?
parsed = PlistLite.load(plist_string)
parsed.map{ _1['Available'] }
How do I update the value and save the plist?
parsed = PlistLite.load(plist_string)
parsed.first['Available']
IO.write 'test.plist', PlistLite.dump(parsed)
How can I retrieve the value of fooVersion from the XML snippet below and put it into a BASH variable please?
<?xml version="1.0" encoding="UTF-8"?>
<TestPlan version="1.2" properties="2.8" fooVersion="2.13 r1665067">
Not tested, based on my own script:
fooVersion=`xmlstarlet sel -T -E utf-8 -t -m /TestPlan -v #fooVersion`
See here for more examples.
This answer about CopySymbolicKeys() gets you the keyboard combinations for System Shortcuts in OS X, but is there any way to get at the labels associated with those combinations?
For example, I can get ⌥⌘D from CopySymbolicKeys() but I want to get "Turn Dock Hiding On/Off", its associated label in System Preferences > Keyboard > Shortcuts.
I'm think it's unlikely, but remain hopeful.
As far as I know, there is no API for that, but it is possible to reconstruct what you want. It will need some work from you though.
You can get the names of the symbolic keys here:
Earlier macOS versions: System/Library/PreferencePanes/Keyboard.prefPane/Contents/Resources/en.lproj/DefaultShortcutsTable.xml
macOS 13 Ventura: /System/Library/ExtensionKit/Extensions/KeyboardSettings.appex/Contents/Resources/en.lproj/DefaultShortcutsTable.xml
You need to parse this XML file and then find the corresponding hotkey here:
~/Library/Preferences/com.apple.symbolichotkeys.plist
The matching must be done between key sybmolichotkey (yes, the key is misspelled like that!) in the aforementioned XML file with key called key containing the same number in the latter plist file.
Example taken from the DefaultShortcutsTable.xml file:
<dict>
<key>name</key>
<string>DO_NOT_LOCALIZE: Dashboard and Dock</string>
<key>ax_description</key>
<string>DO_NOT_LOCALIZE: Dashboard and Dock shortcuts (AX_DESCRIPTION)</string>
<key>identifier</key>
<string>dock</string>
<key>iconname</key>
<string>category_dock</string>
<key>icon-bundle-path</key>
<string>/System/Library/CoreServices/Dock.app</string>
<key>canRestoreDefaults</key>
<true/>
<key>elements</key>
<array>
<dict>
<key>name</key>
<string>DO_NOT_LOCALIZE: Turn Dock Hiding On/Off</string>
<key>key</key>
<integer>2</integer>
<key>modifier</key>
<integer>1572864</integer>
<key>sybmolichotkey</key> <--- look for this key
<integer>52</integer> <--- and its value
<key>charKey</key>
<integer>100</integer>
</dict>
<dict>
<key>name</key>
<string>DO_NOT_LOCALIZE: Show Launchpad</string>
<key>key</key>
<integer>65535</integer>
<key>modifier</key>
<integer>0</integer>
<key>sybmolichotkey</key> <--- look for this key
<integer>160</integer> <--- and its value
</dict>
</array>
</dict>
And then look for the same number in com.apple.symbolichotkeys.plist:
<key>52</key>
<dict>
<key>enabled</key>
<true/>
<key>value</key>
<dict>
<key>parameters</key>
<array>
<integer>65535</integer>
<integer>2</integer>
<integer>1572864</integer>
</array>
<key>type</key>
<string>standard</string>
</dict>
</dict>
Major caveat: the latter plist is in binary form. To convert it to text, you need to do this in Terminal:
plutil -convert xml1 ~/Library/Preferences/com.apple.symbolichotkeys.plist
I have a LaunchAgent which invokes an application when the user logs in. The application loads a website.
LaunchAgent
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>LaunchAgent.Test</string>
<key>RunAtLoad</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>/Applications/MyApp.app/Contents/MacOS/applet</string>
</array>
</dict>
</plist>
Application
display alert set_cookies_for_URL("http://www.apple.com")
(* AppleScript's handlers all seem to become unusable after importing frameworks.
* To compensate, I'm relegating AppleScript/ObjC calls to the end of the file
*)
use framework "WebKit"
on set_cookies_for_URL(URL)
set web_view to current application's WebView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 500, 500)) frameName:"tempFrame" groupName:"tempGroup"
set web_view's mainFrameURL to URL
#delay to avoid release
repeat while ((web_view's isLoading) as boolean) is true
delay 1
end repeat
return "done"
end set_cookies_for_URL
How would I go about passing the website parameter into the application from the LaunchAgent?
You can use this. The second argument (after the executable) should be "-" then the third should be "http://www.apple.com" or whatever you want it to be:
use framework "WebKit"
use scripting additions
set argv to (current application's NSProcessInfo's processInfo()'s arguments as list)
if (count of argv) is 3 then
set_cookies_for_URL(item 3 of argv)
end if
quit
on set_cookies_for_URL(URL)
set web_view to current application's WebView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 500, 500)) frameName:"tempFrame" groupName:"tempGroup"
set web_view's mainFrameURL to URL
#delay to avoid release
repeat while ((web_view's isLoading) as boolean) is true
delay 1
end repeat
end set_cookies_for_URL
EDIT: Oh yes, and I figured out why AppleScript wasn't recognizing "display alert". You have to declare "use scripting additions" to enable full AppleScript when importing a framework..
I want to change the value of DEFAULT_VALUE_PLACEHOLDER in the following plist using the command line tool defaults
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>DefaultValue</key>
<string>DEFAULT_VALUE_PLACEHOLDER</string>
<key>Type</key>
<string>PSTitleValueSpecifier</string>
<key>Title</key>
<string>Version</string>
<key>Key</key>
<string>prefs_item_version_title</string>
</dict>
</array>
<key>StringsTable</key>
<string>Root</string>
</dict>
</plist>
I realise that a simple find and replace will do it (e.g. sed), however, I want a more robust way of doing it.
I think is something like this, but the documentation for the syntax isn't good enough.
defaults write $PLIST_PATH 'PreferenceSpecifiers { 1 = { DefaultValue = $NEW_DETAULT_VALUE; }; }'
I don't think there's any way to do this with defaults (that isn't completely ugly) -- you're better off doing things like this with PlistBuddy instead:
/usr/libexec/PlistBuddy -c "set :PreferenceSpecifiers:0:DefaultValue '$NEW_DEFAULT_VALUE'" "$PLIST_PATH"
Note that unlike defaults, PlistBuddy expects the filename you give it to include the ".plist"; also, (as seen above), array indexes start at 0.