+# See "2.2 Characters" in the XML specification:
+#
+# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# avoiding
+# [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
+
+invalid_codepoints = range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)
+# broke with f24, somehow we get a unicode as an incoming string to be translated
+str_xml_escape_table = string.maketrans("".join((chr(x) for x in invalid_codepoints)),
+ "?" * len(invalid_codepoints))
+# loosely inspired from
+# http://www.terminally-incoherent.com/blog/2010/05/06/character-mapping-must-return-integer-none-or-unicode/
+unicode_xml_escape_table = { invalid : u"?" for invalid in invalid_codepoints}
+
+def xmlrpclib_escape(s, replace = string.replace):
+ """
+ xmlrpclib does not handle invalid 7-bit control characters. This
+ function augments xmlrpclib.escape, which by default only replaces
+ '&', '<', and '>' with entities.
+ """
+
+ # This is the standard xmlrpclib.escape function
+ s = replace(s, "&", "&")
+ s = replace(s, "<", "<")
+ s = replace(s, ">", ">",)
+
+ # Replace invalid 7-bit control characters with '?'
+ if isinstance(s, str):
+ return s.translate(str_xml_escape_table)
+ else:
+ return s.translate(unicode_xml_escape_table)
+
+def test_xmlrpclib_escape():
+ inputs = [
+ # full ASCII
+ "".join( (chr(x) for x in range(128))),
+ # likewise but as a unicode string up to 256
+ u"".join( (unichr(x) for x in range(256))),
+ ]
+ for input in inputs:
+ print "==================== xmlrpclib_escape INPUT"
+ print type(input), '->', input
+ print "==================== xmlrpclib_escape OUTPUT"
+ print xmlrpclib_escape(input)
+
+def xmlrpclib_dump(self, value, write):