plistlib – Mac OS X のプロパティリストファイルを扱う

目的:Mac OS X のプロパティリストファイルを読み書きする
利用できるバージョン:2.6

plistlib は、Mac OS X 環境で使用されるプロパティリストを扱うインタフェースを提供します。plist ファイルは、普通は XML ファイルですが、たまに圧縮されています。それは OS やアプリケーションが設定を保存するために使用します。そのコンテンツは、通常、基本的な組み込み型(ユニコード文字列、整数、日付等)のキーと値を含むディクショナリとして構成されます。また、その値は他のディクショナリやリストを含むネストしたデータ構造でも構いません。バイナリデータや制御文字を含む文字列は data 型でエンコードされます。

plist ファイルを読み込む

iCal のような Mac OS X アプリケーションは、管理するオブジェクトについてのメタデータを保存するために plist ファイルを使用します。例えば、iCal はライブラリディクショナリに一連の plist ファイルとしてカレンダーの全ての定義を格納します。

<?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>

このサンプルスクリプトは、plist ファイルを読み込んでからカレンダー定義を探して、iCal が表示する(Checked プロパティが True にセットされている)任意のカレンダーのタイトルを表示します。

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']

Checked プロパティの型は plist ファイルによって定義されるので、このサンプルスクリプトが文字列から整数へ変換する必要はありません。

$ 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

plist ファイルを書き込む

もし plist ファイルに独自の設定を保存したいなら、データをシリアライズしてファイルシステム上のファイルへ書き込むのに writePlist() を使用してください。

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()
    

1番目の引数は書き込むデータ構造で、2番目の引数はオープンされたファイルハンドラか、ファイル名です。

$ python plistlib_write_plist.py
<?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>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.9</real>
        <key>timestamp</key>
        <date>2013-02-17T11:33:10Z</date>
        <key>xml_string</key>
        <string>&lt;element attr="value"&gt;This string includes XML markup &amp;nbsp;&lt;/element&gt;</string>
</dict>
</plist>

バイナリプロパティデータ

plist を使用して制御文字を含む可能性がある文字列やバイナリデータをシリアライズすることは、XML フォーマットの典型的な問題を回避しません。この問題のワークアラウンドとして、そのオブジェクトが Data インスタンスでラップされる場合、plist ファイルは base64 フォーマットでバイナリデータを格納できます。

import plistlib

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

print plistlib.writePlistToString(d)

このサンプルは、ファイルへ書き込むのではなく、インメモリ文字列を作成するために writePlistToString() を使用します。

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

バイナリデータは、読み込み時に自動的に Data インスタンスに変換されます。

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)

このオブジェクトの data 属性はデコードされたデータを含みます。

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

See also

plistlib
本モジュールの標準ライブラリドキュメント
plist manual page
plist ファイルフォーマットのドキュメント
Weather Underground
ICS や RSS フィードを提供するフリーの天気情報
Convert plist between XML and Binary formats
plist ファイルによっては XML ではなくバイナリフォーマットに格納されます。それはバイナリフォーマットの方が Apple のライブラリを使用して解析するのが速いからです。Python の plistlib モジュールはバイナリフォーマットを扱わないので、バイナリファイルを読み込む前に plutil で XML ファイルに変換する必要があるかもしれません。
Using Python for System Administration
ネイティブの Cocoa API で plist をロードするために PyObjC 使用の詳細についての Nigel Kersten と Chris Adams の発表内容です。それは透過的に XML とバイナリフォーマットの両方を扱います。特に 27 ページ以降が参考になります。
Bookmark and Share