How to force plistlib in Python to save escaped special characters?

166 views Asked by At

I am having a kind of a problem with module plistlib. It works fine, except when saving plists. It doesn't save apostrophe as special character. It does save & as & which is fine, but it saves apostrophe as ' (instead of ') which is not fine. I have a lot of plists with a lot of text and when I change something (batch change with script) it gives me a headache with git diff, because every single ' will become '.

How to force plistlib to save plist with all special characters escaped (after all, there is only 5 of them)?

1

There are 1 answers

0
dosvarog On BEST ANSWER

I will answer my own question since I dug around and found answer.

Problem is with function _escape(text) in module plistlib. It escapes only &, < and > although Xcode with its plist reader escapes all five characters (&, <, >, ' and "), that is why I think this module should also. It would also be a good addition to module plistlib something like ElementTree's parameter entities in function escape(). That parameter is a dict with additional characters to replace. Similar addition to plistlib's function save_plist() would be a good idea so that we can escape additional characters.

My solution is based on so called monkey patching. Basically, I copied whole function _escape(text) and just added additional escapes (' and "):

from plistlib import _controlCharPat

def patched_escape(text):
    m = _controlCharPat.search(text)
    if m is not None:
        raise ValueError("strings can't contains control characters; "
                         "use bytes instead")
    text = text.replace("\r\n", "\n")       # convert DOS line endings
    text = text.replace("\r", "\n")         # convert Mac line endings
    text = text.replace("&", "&amp;")       # escape '&'
    text = text.replace("<", "&lt;")        # escape '<'
    text = text.replace(">", "&gt;")        # escape '>'
    text = text.replace("'", "&apos;")      # escape '''
    text = text.replace("\"", "&quot;")     # escape '"'

    return text

And now in my script I replaced plistlib's function _escape(text) with mine:

plistlib._escape = patched_escape

Now plistlib correctly escapes and saves plists. Usual warnings regarding monkey patching apply also here. I don't have other callers, just this script so it is fine to do this.