Python 2.7.2: plistlib with itunes xml
Question: I'm reading an itunes generated xml playlist with plistib. The xml has a utf8
header.

When I read the xml with plistib, I get both unicode (e.g., 'Name':
u'Don\u2019t You Remember') and byte strings (e.g., 'Name': 'Where Eagles
Dare').

Standard advice is to decode what you read with the correct encoding as soon
as possible and use unicode within the program. However,

    
    
    unicode_string.decode('utf8') 
    

fails (as it should) with

    
    
    UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 3: ordinal not in range(128)
    

The solution would seem to be:

    
    
    for name in names:
        if isinstance(name, str):
            name = name.decode('utf8')
        # etc.
    

Is this the correct way of dealing with the problem? Is there a better way?

I'm on windows 7.

EDIT:

xml read with:

    
    
    import plistlib
    xml = plistlb.readPlist(fn)
    for track in xml['Tracks']:
        info = xml['Tracks'][track]
        info['Name']
    

Produces in idle:

    
    
    u'Don\u2019t You Remember'
    'Where Eagles Dare'
    

Here's the xml file:

    
    
    <?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>Major Version</key><integer>1</integer>
        <key>Minor Version</key><integer>1</integer>
        <key>Date</key><date>2013-08-14T15:04:27Z</date>
        <key>Application Version</key><string>10.6.3</string>
        <key>Features</key><integer>5</integer>
        <key>Show Content Ratings</key><true/>
        <key>Music Folder</key><string>file://localhost/C:/Users/rdp/Music/iTunes/iTunes%20Media/</string>
        <key>Library Persistent ID</key><string>FE28CCACD9A36C34</string>
        <key>Tracks</key>
        <dict>
            <key>1019</key>
            <dict>
                <key>Track ID</key><integer>1019</integer>
                <key>Name</key><string>Where Eagles Dare</string>
                <key>Artist</key><string>Iron Maiden</string>
                <key>Album</key><string>Piece Of Mind</string>
                <key>Genre</key><string>Rock</string>
                <key>Kind</key><string>MPEG audio file</string>
                <key>Size</key><integer>7372755</integer>
                <key>Total Time</key><integer>370128</integer>
                <key>Track Number</key><integer>1</integer>
                <key>Year</key><integer>1983</integer>
                <key>Date Modified</key><date>2009-10-07T21:11:31Z</date>
                <key>Date Added</key><date>2008-02-07T16:04:15Z</date>
                <key>Bit Rate</key><integer>153</integer>
                <key>Sample Rate</key><integer>44100</integer>
                <key>Play Count</key><integer>4</integer>
                <key>Play Date</key><integer>3414416760</integer>
                <key>Play Date UTC</key><date>2012-03-12T21:06:00Z</date>
                <key>Artwork Count</key><integer>1</integer>
                <key>Persistent ID</key><string>FE28CCACD9A383E5</string>
                <key>Track Type</key><string>File</string>
                <key>Location</key><string>file://localhost/D:/music/Iron%20Maiden/Piece%20Of%20Mind/01%20Where%20Eagles%20Dare.mp3</string>
                <key>File Folder Count</key><integer>-1</integer>
                <key>Library Folder Count</key><integer>-1</integer>
            </dict>
            <key>11559</key>
            <dict>
                <key>Track ID</key><integer>11559</integer>
                <key>Name</key><string>Don’t You Remember</string>
                <key>Artist</key><string>Adele</string>
                <key>Album</key><string>21</string>
                <key>Genre</key><string>Pop</string>
                <key>Kind</key><string>MPEG audio file</string>
                <key>Size</key><integer>6120028</integer>
                <key>Total Time</key><integer>229511</integer>
                <key>Track Number</key><integer>4</integer>
                <key>Track Count</key><integer>11</integer>
                <key>Year</key><integer>2011</integer>
                <key>Date Modified</key><date>2012-11-17T10:50:31Z</date>
                <key>Date Added</key><date>2012-12-19T16:03:46Z</date>
                <key>Bit Rate</key><integer>199</integer>
                <key>Sample Rate</key><integer>44100</integer>
                <key>Artwork Count</key><integer>1</integer>
                <key>Persistent ID</key><string>7130C888606FB153</string>
                <key>Track Type</key><string>File</string>
                <key>Location</key><string>file://localhost/D:/music/Adele/21/04%20-%20Don%E2%80%99t%20You%20Remember.mp3</string>
                <key>File Folder Count</key><integer>-1</integer>
                <key>Library Folder Count</key><integer>-1</integer>
            </dict>
        </dict>
        <key>Playlists</key>
        <array>
            <dict>
                <key>Name</key><string>short</string>
                <key>Playlist ID</key><integer>30888</integer>
                <key>Playlist Persistent ID</key><string>166746C6572B0005</string>
                <key>All Items</key><true/>
                <key>Playlist Items</key>
                <array>
                    <dict>
                        <key>Track ID</key><integer>11559</integer>
                    </dict>
                    <dict>
                        <key>Track ID</key><integer>1019</integer>
                    </dict>
                </array>
            </dict>
        </array>
    </dict>
    </plist>
    


Answer: Wow this is a really weird behaviour. I would even say that this non-uniform
behaviour is a bug in the 2.X implementation of the `plistlib`. The `plistlib`
in Python 3 always returns unicode strings which is much better.

But you have to live with it :) So the answer to your question is yes. You
should protect yourself always when reading a string from a `plist`

    
    
    def safe_unicode(s):
        if isinstance(s, unicode):
            return s
        return s.decode('utf-8', errors='replace')
    
    value = safe_unicode(info['Name'])
    

I added the `errors='replace'` just in case the string is not `utf-8` encoded.
You'll get a bunch of `\ufffd` characters if it cannot be decoded. If you
rather get an exception just leave it out and use `e.decode('utf-8')`.

_Update:_

When I tried with ElementTree:

    
    
    from xml.etree import ElementTree as et
    tree = et.parse('test.plist')
    map(lambda x: x.text, tree.findall('dict/dict/dict')[1].findall('string'))
    

Which gave me:

    
    
    [u'Don\u2019t You Remember',
     'Adele',
     '21',
     'Pop',
     'MPEG audio file',
     '7130C888606FB153',
     'File',
     'file://localhost/D:/music/Adele/21/04%20-%20Don%E2%80%99t%20You%20Remember.mp3']
    

So there are unicode and byte string mixed :-/

