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)?
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("&", "&") # escape '&'
text = text.replace("<", "<") # escape '<'
text = text.replace(">", ">") # escape '>'
text = text.replace("'", "'") # escape '''
text = text.replace("\"", """) # 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.