[Python-checkins] python/dist/src/Lib/plat-mac plistlib.py,1.4,1.5

jvr at users.sourceforge.net jvr at users.sourceforge.net
Sat Oct 2 10:40:49 CEST 2004


Update of /cvsroot/python/python/dist/src/Lib/plat-mac
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv14988

Modified Files:
	plistlib.py 
Log Message:
Which reminds me, I've had a much improved plistlib.py lying around for
ages. The main improvements are:
- a much more convenient API: readPlist() and writePlist()
- support non-dict top-level objects


Index: plistlib.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/plat-mac/plistlib.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- plistlib.py	10 Jul 2003 14:26:06 -0000	1.4
+++ plistlib.py	2 Oct 2004 08:40:47 -0000	1.5
@@ -1,15 +1,16 @@
 """plistlib.py -- a tool to generate and parse MacOSX .plist files.
 
-The main class in this module is Plist. It takes a set of arbitrary
-keyword arguments, which will be the top level elements of the plist
-dictionary. After instantiation you can add more elements by assigning
-new attributes to the Plist instance.
+The PropertList (.plist) file format is a simple XML pickle supporting
+basic object types, like dictionaries, lists, numbers and strings.
+Usually the top level object is a dictionary.
 
-To write out a plist file, call the write() method of the Plist
-instance with a filename or a file object.
+To write out a plist file, use the writePlist(rootObject, pathOrFile)
+function. 'rootObject' is the top level object, 'pathOrFile' is a
+filename or a (writable) file object.
 
-To parse a plist from a file, use the Plist.fromFile(pathOrFile)
-classmethod, with a file name or a file object as the only argument.
+To parse a plist from a file, use the readPlist(pathOrFile)
+function, with a file name or a (readable) file object as the only
+argument. It returns the top level object (usually a dictionary).
 (Warning: you need pyexpat installed for this to work, ie. it doesn't
 work with a vanilla Python 2.2 as shipped with MacOS X.2.)
 
@@ -17,9 +18,10 @@
 dictionaries, Data or Date objects. String values (including dictionary
 keys) may be unicode strings -- they will be written out as UTF-8.
 
-For convenience, this module exports a class named Dict(), which
-allows you to easily construct (nested) dicts using keyword arguments.
-But regular dicts work, too.
+This module exports a class named Dict(), which allows you to easily
+construct (nested) dicts using keyword arguments as well as accessing
+values with attribute notation, where d.foo is equivalent to d["foo"].
+Regular dictionaries work, too.
 
 To support Boolean values in plists with Python < 2.3, "bool", "True"
 and "False" are exported. Use these symbols from this module if you
@@ -33,7 +35,7 @@
 
 Generate Plist example:
 
-    pl = Plist(
+    pl = Dict(
         aString="Doodah",
         aList=["A", "B", 12, 32.1, [1, 2, 3]],
         aFloat = 0.1,
@@ -50,31 +52,62 @@
     )
     # unicode keys are possible, but a little awkward to use:
     pl[u'\xc5benraa'] = "That was a unicode key."
-    pl.write(fileName)
+    writePlist(pl, fileName)
 
 Parse Plist example:
 
-    pl = Plist.fromFile(pathOrFile)
-    print pl.aKey
+    pl = readPlist(pathOrFile)
+    print pl.aKey  # same as pl["aKey"]
 
 
 """
 
-# written by Just van Rossum (just at letterror.com), 2002-11-19
+
+__all__ = ["readPlist", "writePlist", "Plist", "Data", "Date", "Dict",
+           "False", "True", "bool"]
+# Note: the Plist class has been deprecated, Dict, False, True and bool
+# are here only for compatibility with Python 2.2.
 
 
-__all__ = ["Plist", "Data", "Date", "Dict", "False", "True", "bool"]
+def readPlist(pathOrFile):
+    """Read a .plist file. 'pathOrFile' may either be a file name or a
+    (readable) file object. Return the unpacked root object (which
+    usually is a dictionary).
+    """
+    didOpen = 0
+    if isinstance(pathOrFile, (str, unicode)):
+        pathOrFile = open(pathOrFile)
+        didOpen = 1
+    p = PlistParser()
+    rootObject = p.parse(pathOrFile)
+    if didOpen:
+        pathOrFile.close()
+    return rootObject
 
 
-INDENT = "\t"
+def writePlist(rootObject, pathOrFile):
+    """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
+    file name or a (writable) file object.
+    """
+    didOpen = 0
+    if isinstance(pathOrFile, (str, unicode)):
+        pathOrFile = open(pathOrFile, "w")
+        didOpen = 1
+    writer = PlistWriter(pathOrFile)
+    writer.writeln("<plist version=\"1.0\">")
+    writer.writeValue(rootObject)
+    writer.writeln("</plist>")
+    if didOpen:
+        pathOrFile.close()
 
 
 class DumbXMLWriter:
 
-    def __init__(self, file):
+    def __init__(self, file, indentLevel=0, indent="\t"):
         self.file = file
         self.stack = []
-        self.indentLevel = 0
+        self.indentLevel = indentLevel
+        self.indent = indent
 
     def beginElement(self, element):
         self.stack.append(element)
@@ -89,22 +122,30 @@
 
     def simpleElement(self, element, value=None):
         if value is not None:
-            value = _encode(value)
+            value = _escapeAndEncode(value)
             self.writeln("<%s>%s</%s>" % (element, value, element))
         else:
             self.writeln("<%s/>" % element)
 
     def writeln(self, line):
         if line:
-            self.file.write(self.indentLevel * INDENT + line + "\n")
+            self.file.write(self.indentLevel * self.indent + line + "\n")
         else:
             self.file.write("\n")
 
 
-def _encode(text):
-    text = text.replace("&", "&amp;")
-    text = text.replace("<", "&lt;")
-    return text.encode("utf-8")
+import re
+# Regex to strip all control chars, but for \t \n \r and \f
+_controlStripper = re.compile(r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0e\x0f"
+    "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
+
+def _escapeAndEncode(text):
+    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 = _controlStripper.sub("?", text)  # replace control chars with '?'
+    return text.encode("utf-8")             # encode as UTF-8
 
 
 PLISTHEADER = """\
@@ -114,9 +155,10 @@
 
 class PlistWriter(DumbXMLWriter):
 
-    def __init__(self, file):
-        file.write(PLISTHEADER)
-        DumbXMLWriter.__init__(self, file)
+    def __init__(self, file, indentLevel=0, indent="\t", writeHeader=1):
+        if writeHeader:
+            file.write(PLISTHEADER)
+        DumbXMLWriter.__init__(self, file, indentLevel, indent)
 
     def writeValue(self, value):
         if isinstance(value, (str, unicode)):
@@ -133,7 +175,7 @@
         elif isinstance(value, float):
             # should perhaps use repr() for better precision?
             self.simpleElement("real", str(value))
-        elif isinstance(value, (dict, Dict)):
+        elif isinstance(value, dict):
             self.writeDict(value)
         elif isinstance(value, Data):
             self.writeData(value)
@@ -169,66 +211,55 @@
         self.endElement("array")
 
 
-class Dict:
+class Dict(dict):
 
-    """Dict wrapper for convenient access of values through attributes."""
+    """Convenience dictionary subclass: it allows dict construction using
+    keyword arguments (just like dict() in 2.3) as well as attribute notation
+    to retrieve values, making d.foo more or less uquivalent to d["foo"].
+    """
 
-    def __init__(self, **kwargs):
-        self.__dict__.update(kwargs)
+    def __new__(cls, **kwargs):
+        self = dict.__new__(cls)
+        self.update(kwargs)
+        return self
 
-    def __cmp__(self, other):
-        if isinstance(other, self.__class__):
-            return cmp(self.__dict__, other.__dict__)
-        elif isinstance(other, dict):
-            return cmp(self.__dict__, other)
-        else:
-            return cmp(id(self), id(other))
+    def __init__(self, **kwargs):
+        self.update(kwargs)
 
-    def __str__(self):
-        return "%s(**%s)" % (self.__class__.__name__, self.__dict__)
-    __repr__ = __str__
+    def __getattr__(self, attr):
+        try:
+            value = self[attr]
+        except KeyError:
+            raise AttributeError, attr
+        return value
 
-    def copy(self):
-        return self.__class__(**self.__dict__)
+    def __setattr__(self, attr, value):
+        self[attr] = value
 
-    def __getattr__(self, attr):
-        """Delegate everything else to the dict object."""
-        return getattr(self.__dict__, attr)
+    def __delattr__(self, attr):
+        try:
+            del self[attr]
+        except KeyError:
+            raise AttributeError, attr
 
 
 class Plist(Dict):
 
-    """The main Plist object. Basically a dict (the toplevel object
-    of a plist is a dict) with two additional methods to read from
-    and write to files.
+    """This class has been deprecated! Use the Dict with readPlist() and
+    writePlist() functions instead.
     """
 
     def fromFile(cls, pathOrFile):
-        didOpen = 0
-        if isinstance(pathOrFile, (str, unicode)):
-            pathOrFile = open(pathOrFile)
-            didOpen = 1
-        p = PlistParser()
-        plist = p.parse(pathOrFile)
-        if didOpen:
-            pathOrFile.close()
+        """Deprecated! Use the readPlist() function instead."""
+        rootObject = readPlist(pathOrFile)
+        plist = cls()
+        plist.update(rootObject)
         return plist
     fromFile = classmethod(fromFile)
 
     def write(self, pathOrFile):
-        if isinstance(pathOrFile, (str, unicode)):
-            pathOrFile = open(pathOrFile, "w")
-            didOpen = 1
-        else:
-            didOpen = 0
-
-        writer = PlistWriter(pathOrFile)
-        writer.writeln("<plist version=\"1.0\">")
-        writer.writeDict(self.__dict__)
-        writer.writeln("</plist>")
-
-        if didOpen:
-            pathOrFile.close()
+        """Deprecated! Use the writePlist() function instead."""
+        writePlist(self, pathOrFile)
 
 
 class Data:
@@ -323,7 +354,7 @@
             self.currentKey = None
         elif not self.stack:
             # this is the root object
-            assert self.root is value
+            self.root = value
         else:
             self.stack[-1].append(value)
 
@@ -339,10 +370,7 @@
     # element handlers
 
     def begin_dict(self, attrs):
-        if self.root is None:
-            self.root = d = Plist()
-        else:
-            d = Dict()
+        d = Dict()
         self.addObject(d)
         self.stack.append(d)
     def end_dict(self):
@@ -401,7 +429,7 @@
     from StringIO import StringIO
     import time
     if len(sys.argv) == 1:
-        pl = Plist(
+        pl = Dict(
             aString="Doodah",
             aList=["A", "B", 12, 32.1, [1, 2, 3]],
             aFloat = 0.1,
@@ -414,10 +442,10 @@
             ),
             someData = Data("<binary gunk>"),
             someMoreData = Data("<lots of binary gunk>" * 10),
-            aDate = Date(time.mktime(time.gmtime())),
+#            aDate = Date(time.mktime(time.gmtime())),
         )
     elif len(sys.argv) == 2:
-        pl = Plist.fromFile(sys.argv[1])
+        pl = readPlist(sys.argv[1])
     else:
         print "Too many arguments: at most 1 plist file can be given."
         sys.exit(1)
@@ -425,13 +453,13 @@
     # unicode keys are possible, but a little awkward to use:
     pl[u'\xc5benraa'] = "That was a unicode key."
     f = StringIO()
-    pl.write(f)
+    writePlist(pl, f)
     xml = f.getvalue()
     print xml
     f.seek(0)
-    pl2 = Plist.fromFile(f)
+    pl2 = readPlist(f)
     assert pl == pl2
     f = StringIO()
-    pl2.write(f)
+    writePlist(pl2, f)
     assert xml == f.getvalue()
     #print repr(pl2)



More information about the Python-checkins mailing list