Adjust hooke.playlist to allow spaces in .info keys.
authorW. Trevor King <wking@drexel.edu>
Mon, 2 Aug 2010 17:48:39 +0000 (13:48 -0400)
committerW. Trevor King <wking@drexel.edu>
Mon, 2 Aug 2010 17:48:39 +0000 (13:48 -0400)
hooke/playlist.py

index b58e9b6fa7f49be199780279bb8dc358f4ff2fe7..bba2990604927b1fbcdc624dfe7992a94e678295 100644 (file)
@@ -161,6 +161,26 @@ class FilePlaylist (Playlist):
         string = self.flatten()
         return hashlib.sha1(string).digest()
 
+    def _clean_key(self, key):
+        """Replace spaces in keys with \\u00B7 (middle dot).
+
+        This character is deemed unlikely to occur in keys to our
+        playlist and curve info dictionaries, while many keys have
+        spaces in them.
+
+        \\u00B7 is allowed in XML 1.0 as of the 5th edition.  See
+        the `4th edition errata`_ for details.
+
+        .. _4th edition errata:
+          http://www.w3.org/XML/xml-V10-4e-errata#E09
+        """
+        return key.replace(' ', u'\u00B7')
+
+    def _restore_key(self, key):
+        """Restore keys encoded with :meth:`_clean_key`.
+        """
+        return key.replace(u'\u00B7', ' ')
+
     def flatten(self, absolute_paths=False):
         """Create a string representation of the playlist.
 
@@ -186,22 +206,23 @@ class FilePlaylist (Playlist):
         >>> c.info['note'] = 'The first curve'
         >>> p.append(c)
         >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'two'))
-        >>> c.info['note'] = 'The second curve\\nwith endlines'
+        >>> c.info['attr with spaces'] = 'The second curve\\nwith endlines'
         >>> p.append(c)
-        >>> print p.flatten() # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
+        >>> def _print(string):
+        ...     escaped_string = unicode(string, 'utf-8').encode('unicode escape')
+        ...     print escaped_string.replace('\\\\n', '\\n').replace('\\\\t', '\\t'),
+        >>> _print(p.flatten())  # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
         <?xml version="1.0" encoding="utf-8"?>
         <playlist index="0" note="An example playlist" version="0.1">
            <curve note="The first curve" path="curve/one"/>
-           <curve note="The second curve&#xA;with endlines" path="curve/two"/>
+           <curve attr\\xb7with\\xb7spaces="The second curve&#xA;with endlines" path="curve/two"/>
         </playlist>
-        <BLANKLINE>
-        >>> print p.flatten(absolute_paths=True) # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
+        >>> _print(p.flatten(absolute_paths=True))  # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
         <?xml version="1.0" encoding="utf-8"?>
         <playlist index="0" note="An example playlist" version="0.1">
            <curve note="The first curve" path="/path/to/curve/one"/>
-           <curve note="The second curve&#xA;with endlines" path="/path/to/curve/two"/>
+           <curve attr\\xb7with\\xb7spaces="The second curve&#xA;with endlines" path="/path/to/curve/two"/>
         </playlist>
-        <BLANKLINE>
         """
         implementation = xml.dom.minidom.getDOMImplementation()
         # create the document DOM object and the root element
@@ -210,7 +231,7 @@ class FilePlaylist (Playlist):
         root.setAttribute('version', self.version) # store playlist version
         root.setAttribute('index', str(self._index))
         for key,value in self.info.items(): # save info variables
-            root.setAttribute(key, str(value))
+            root.setAttribute(self._clean_key(key), str(value))
         for curve in self: # save curves and their attributes
             curve_element = doc.createElement('curve')
             root.appendChild(curve_element)
@@ -225,7 +246,7 @@ class FilePlaylist (Playlist):
             for key,value in curve.info.items():
                 if key in self._ignored_keys:
                     continue
-                curve_element.setAttribute(key,str(value))
+                curve_element.setAttribute(self._clean_key(key), str(value))
         string = doc.toprettyxml(encoding='utf-8')
         root.unlink() # break circular references for garbage collection
         return string
@@ -236,6 +257,7 @@ class FilePlaylist (Playlist):
         """
         root = doc.documentElement
         for attribute,value in root.attributes.items():
+            attribute = self._restore_key(attribute)
             if attribute == 'version':
                 assert value == self.version, \
                     'Cannot read v%s playlist with a v%s reader' \
@@ -246,13 +268,14 @@ class FilePlaylist (Playlist):
                 self.info[attribute] = value
         for curve_element in doc.getElementsByTagName('curve'):
             path = curve_element.getAttribute('path')
-            info = dict(curve_element.attributes.items())
+            info = dict([(self._restore_key(key), value)
+                         for key,value in curve_element.attributes.items()])
             info.pop('path')
             self.append_curve_by_path(path, info, identify=identify)
         self.jump(self._index) # ensure valid index
 
     def from_string(self, string, identify=True):
-        """Load a playlist from a string.
+        u"""Load a playlist from a string.
 
         Examples
         --------
@@ -260,7 +283,7 @@ class FilePlaylist (Playlist):
         >>> string = '''<?xml version="1.0" encoding="utf-8"?>
         ... <playlist index="1" note="An example playlist" version="0.1">
         ...     <curve note="The first curve" path="../curve/one"/>
-        ...     <curve note="The second curve&#xA;with endlines" path="../curve/two"/>
+        ...     <curve attr\xb7with\xb7spaces="The second curve&#xA;with endlines" path="../curve/two"/>
         ... </playlist>
         ... '''
         >>> p = FilePlaylist(drivers=[],
@@ -274,7 +297,7 @@ class FilePlaylist (Playlist):
         ...     print curve.path
         path/to/curve/one
         path/to/curve/two
-        >>> p[-1].info['note']
+        >>> p[-1].info['attr with spaces']
         u'The second curve\\nwith endlines'
         """
         doc = xml.dom.minidom.parseString(string)