Sunday, November 29, 2009

PyMOTW: plistlib - Manipulate OS X property list files

plistlib – Manipulate OS X property list files

Purpose:Read and write OS X property list files
Python Version:2.6

plistlib provides an interface for working with property list files
used under OS X. plist files are typically XML, sometimes compressed.
They are used by the operating system and applications to store
preferences or other configuration settings. The contents are usually
structured as a dictionary containing key value pairs of basic
built-in types (unicode strings, integers, dates, etc.). Values can
also be nested data structures such as other dictionaries or lists.
Binary data, or strings with control characters, can be encoded using
the data type.

Reading plist Files

OS X applications such as iCal use plist files to store meta-data
about objects they manage. For example, iCal stores the definitions
of all of your calendars as a series of plist files in the Library
directory.

<?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>AlarmFilter</key>
<true/>
<key>AlarmsDisabled</key>
<false/>
<key>AttachmentFilter</key>
<true/>
<key>AutoRefresh</key>
<true/>
<key>Checked</key>
<integer>1</integer>
<key>Color</key>
<string>#808000FF</string>
<key>Enabled</key>
<true/>
<key>Key</key>
<string>4221BCE5-1017-4EE4-B7FF-311A846C600D</string>
<key>NeedsForcedUpdate</key>
<false/>
<key>NeedsRefresh</key>
<true/>
<key>Order</key>
<integer>25</integer>
<key>RefreshDate</key>
<date>2009-11-29T16:31:53Z</date>
<key>RefreshInterval</key>
<integer>3600</integer>
<key>SubscriptionTitle</key>
<string>Athens, GA Weather - By Weather Underground</string>
<key>SubscriptionURL</key>
<string>http://ical.wunderground.com/auto/ical/GA/Athens.ics?units=both</string>
<key>TaskFilter</key>
<true/>
<key>Title</key>
<string>Athens, GA Weather - By Weather Underground</string>
<key>Type</key>
<string>Subscription</string>
</dict>
</plist>

This sample script finds the calendar defintions, reads
them, and prints the titles of any calendars being displayed by iCal
(having the property Checked set to a true value).

import plistlib
import os
import glob

calendar_root = os.path.expanduser('~/Library/Calendars')
calendar_directories = (
glob.glob(os.path.join(calendar_root, '*.caldav', '*.calendar')) +
glob.glob(os.path.join(calendar_root, '*.calendar'))
)

for dirname in calendar_directories:
info_filename = os.path.join(dirname, 'Info.plist')
if os.path.isfile(info_filename):
info = plistlib.readPlist(info_filename)
if info.get('Checked'):
print info['Title']

The type of the Checked property is defined by the plist file, so
our script does not need to convert the string to an integer.

$ python plistlib_checked_calendars.py
Doug Hellmann
Tasks
Vacation Schedule
EarthSeasons
US Holidays
Athens, GA Weather - By Weather Underground
Birthdays
Georgia Bulldogs Calendar (NCAA Football)
Home
Meetup: Django
Meetup: Python

Writing plist Files

If you want to use plist files to save your own settings, use
writePlist() to serialize the data and write it to the filesystem.

import plistlib
import datetime
import tempfile

d = { 'an_int':2,
'a_bool':False,
'the_float':5.9,
'simple_string':'This string has no special characters.',
'xml_string':'<element attr="value">This string includes XML markup &nbsp;</element>',
'nested_list':['a', 'b', 'c'],
'nested_dict':{ 'key':'value' },
'timestamp':datetime.datetime.now(),
}

output_file = tempfile.NamedTemporaryFile()
try:
plistlib.writePlist(d, output_file)
output_file.seek(0)
print output_file.read()
finally:
output_file.close()

The first argument is the data structure to write out, and the second
is an open file handle or the name of a file.

$ python plistlib_write_plist.py
<?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>a_bool</key>
<false/>
<key>an_int</key>
<integer>2</integer>
<key>nested_dict</key>
<dict>
<key>key</key>
<string>value</string>
</dict>
<key>nested_list</key>
<array>
<string>a</string>
<string>b</string>
<string>c</string>
</array>
<key>simple_string</key>
<string>This string has no special characters.</string>
<key>the_float</key>
<real>5.9000000000000004</real>
<key>timestamp</key>
<date>2009-11-29T12:09:35Z</date>
<key>xml_string</key>
<string>&lt;element attr="value"&gt;This string includes XML markup &amp;nbsp;&lt;/element&gt;</string>
</dict>
</plist>


Binary Property Data

Serializing binary data or strings that may include control characters
using a plist is not immune to the typical challenges for an XML
format. To work around the issues, plist files can store binary data
in base64 format if the object is wrapped with a Datainstance.

import plistlib

d = { 'binary_data':plistlib.Data('This data has an embedded null. \0'),
}

print plistlib.writePlistToString(d)

This example uses the ToString version of the write function to create
an in-memory string instead of writing to a file.

$ python plistlib_binary_write.py
<?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>binary_data</key>
<data>
VGhpcyBkYXRhIGhhcyBhbiBlbWJlZGRlZCBudWxsLiAA
</data>
</dict>
</plist>

Binary data is automatically converted to a Data instance when
read.

import plistlib
import pprint

DATA = """<?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>binary_data</key>
<data>
VGhpcyBkYXRhIGhhcyBhbiBlbWJlZGRlZCBudWxsLiAA
</data>
</dict>
</plist>
"""

d = plistlib.readPlistFromString(DATA)

print repr(d['binary_data'].data)

The data attribute of the object contains the decoded data.

$ python plistlib_binary_read.py
'This data has an embedded null. \x00'

See also

plistlib
The standard library documentation for this module.
plist manual page
Documentation of the plist file format.
Weather Underground
Free weather information, including ICS and RSS feeds.
Convert plist between XML and Binary formats
Some plist files are stored in a binary format instead of XML
because the binary format is faster to parse using Apple’s
libraries. Python’s plistlib module does not handle the
binary format, so you may need to convert binary files to XML
using plutil before reading them.

PyMOTW Home

The canonical version of this article

2 comments:

Chris Adams said...

One note: plistlib currently can't handle binary format plists. If you're on a Mac, you can use PyObjC to load those plists using the native Cocoa API, which transparently handles either format - check out slide 27 of our presentation from MWSF this year - in most cases it's basically a matter of doing something like "NSDictionary.dictionaryWithContentsOfFile_(filename)" to get something dict-like.

Doug Hellmann said...

Great tip, Chris, thanks for the link!