Author: duncan
Date: Tue Sep 26 20:32:09 2006
New Revision: 8245

Modified:
   branches/rel-1-5/freevo/setup.py
   branches/rel-1-5/freevo/src/tv/xmltv.py

Log:
[ 1565080 ] python-xmltv to version 1.2, which is much faster
Patch applied, setup also changed to check elementtree.


Modified: branches/rel-1-5/freevo/setup.py
==============================================================================
--- branches/rel-1-5/freevo/setup.py    (original)
+++ branches/rel-1-5/freevo/setup.py    Tue Sep 26 20:32:09 2006
@@ -19,6 +19,7 @@
 check_libs((('mmpython', 'http://www.sf.net/projects/mmpython' ),
             ('pygame', 'http://www.pygame.org'),
             ('Image', 'http://www.pythonware.com/products/pil/'),
+            ('elementtree', 'http://effbot.org/zone/elementtree.htm'),
             ('twisted', 'http://www.twistedmatrix.com/'),
             ('twisted.web.microdom', 'http://www.twistedmatrix.com/')))
 

Modified: branches/rel-1-5/freevo/src/tv/xmltv.py
==============================================================================
--- branches/rel-1-5/freevo/src/tv/xmltv.py     (original)
+++ branches/rel-1-5/freevo/src/tv/xmltv.py     Tue Sep 26 20:32:09 2006
@@ -18,396 +18,250 @@
 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
 #
-#  Notes:
-#  - Uses qp_xml instead of DOM. It's way faster
-#  - Read and write functions use file objects instead of filenames
-#  - Unicode is removed on dictionary keys because the xmlrpclib marshaller
-#    chokes on it. It'll always be Latin-1 anyway... (famous last words)
-#
-#  Yes, A lot of this is quite different than the Perl module, mainly to keep
-#  it Pythonic
-#
 #  If you have any trouble: [EMAIL PROTECTED]
 #
-
-# Changes for Freevo:
-# o change data_format to '%Y%m%d%H%M%S %Z'
-# o change encode to use 'ignore' as error:
-#   string.encode(locale) -> string.encode(locale, 'ignore')
-# o add except AttributeError: for unhandled elements (line 250ff)
-# o import config and change locale = config.LOCALE
-
-from xml.utils import qp_xml
-from xml.sax import saxutils
-import string, types, re
-import config
+import types, re
+try:
+    from cElementTree import ElementTree, Element, SubElement, tostring
+except ImportError:
+    from elementtree.ElementTree import ElementTree, Element, SubElement, 
tostring
 
 # The Python-XMLTV version
-VERSION = "0.5.15"
+VERSION = "1.2"
 
-# The date format used in XMLTV
+# The date format used in XMLTV (the %Z will go away in 0.6)
 date_format = '%Y%m%d%H%M%S %Z'
-# Note: Upstream xmltv.py uses %z so remember to change that when syncing
 date_format_notz = '%Y%m%d%H%M%S'
 
-# These characters are illegal in XML
-XML_BADCHARS = 
re.compile(u'[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]')
-
-
-#
-# Options. They may be overridden after this module is imported
-#
-
-# The locale for dictionary keys. Leaving them in Unicode screws up the
-# XMLRPC marshaller
-locale = config.LOCALE
 
-
-
-# The extraction process could be simpler, building a tree recursively
-# without caring about the element names, but it's done this way to allow
-# special handling for certain elements. If 'desc' changed one day then
-# ProgrammeHandler.desc() can be modified to reflect it
-
-class _ProgrammeHandler:
-    """
-    Handles XMLTV programme elements
+def set_attrs(dict, elem, attrs):
     """
+    set_attrs(dict, elem, attrs) -> None
 
-    #
-    # <tv> sub-tags
-    #
-
-    def title(self, node):
-        return _readwithlang(node)
-
-    def sub_title(self, node):
-        return _readwithlang(node)
-
-    def desc(self, node):
-        return _readwithlang(node)
-
-    def credits(self, node):
-        return _extractNodes(node, self)
-
-    def date(self, node):
-        return node.textof()
-
-    def category(self, node):
-        return _readwithlang(node)
-
-    def language(self, node):
-        return _readwithlang(node)
-
-    def orig_language(self, node):
-        return _readwithlang(node)
-
-    def length(self, node):
-        data = {}
-        data['units'] = _getxmlattr(node, u'units')
-        try:
-            length = int(node.textof())
-        except ValueError:
-            pass
-        data['length'] = length
-        return data
-
-    def icon(self, node):
-        data = {}
-        for attr in (u'src', u'width', u'height'):
-            if node.attrs.has_key(('', attr)):
-                data[attr.encode(locale, 'ignore')] = _getxmlattr(node, attr)
-        return data
-
-    def url(self, node):
-        return node.textof()
-
-    def country(self, node):
-        return _readwithlang(node)
-
-    def episode_num(self, node):
-        system = _getxmlattr(node, u'system')
-        if system == '':
-            system = 'onscreen'
-        return (node.textof(), system)
-
-    def video(self, node):
-        result = {}
-        for child in node.children:
-            result[child.name.encode(locale, 'ignore')] = self._call(child)
-        return result
-
-    def audio(self, node):
-        result = {}
-        for child in node.children:
-            result[child.name.encode(locale, 'ignore')] = self._call(child)
-        return result
-
-    def previously_shown(self, node):
-        data = {}
-        for attr in (u'start', u'channel'):
-            if node.attrs.has_key(('', attr)):
-                data[attr.encode(locale, 'ignore')] = node.attrs[('', attr)]
-        return data
-
-    def premiere(self, node):
-        return _readwithlang(node)
-
-    def last_chance(self, node):
-        return _readwithlang(node)
-
-    def new(self, node):
-        return 1
-
-    def subtitles(self, node):
-        data = {}
-        if node.attrs.has_key(('', u'type')):
-            data['type'] = _getxmlattr(node, u'type')
-        for child in node.children:
-            if child.name == u'language':
-                data['language'] = _readwithlang(child)
-        return data
-
-    def rating(self, node):
-        data = {}
-        data['icon'] = []
-        if node.attrs.has_key(('', u'system')):
-            data['system'] = node.attrs[('', u'system')]
-        for child in node.children:
-            if child.name == u'value':
-                data['value'] = child.textof()
-            elif child.name == u'icon':
-                data['icon'].append(self.icon(child))
-        if len(data['icon']) == 0:
-            del data['icon']
-        return data
-
-    def star_rating(self, node):
-        data = {}
-        data['icon'] = []
-        for child in node.children:
-            if child.name == u'value':
-                data['value'] = child.textof()
-            elif child.name == u'icon':
-                data['icon'].append(self.icon(child))
-        if len(data['icon']) == 0:
-            del data['icon']
-        return data
-
-
-    #
-    # <credits> sub-tags
-    #
-
-    def actor(self, node):
-        return node.textof()
-
-    def director(self, node):
-        return node.textof()
-
-    def writer(self, node):
-        return node.textof()
-
-    def adapter(self, node):
-        return node.textof()
-
-    def producer(self, node):
-        return node.textof()
-
-    def presenter(self, node):
-        return node.textof()
-
-    def commentator(self, node):
-        return node.textof()
-
-    def guest(self, node):
-        return node.textof()
-
-
-    #
-    # <video> and <audio> sub-tags
-    #
-
-    def present(self, node):
-        return _decodeboolean(node)
-
-    def colour(self, node):
-        return _decodeboolean(node)
-
-    def aspect(self, node):
-        return node.textof()
+    Add any attributes in 'attrs' found in 'elem' to 'dict'
+    """
+    for attr in attrs:
+        if attr in elem.keys():
+            dict[attr] = elem.get(attr)
 
-    def stereo(self, node):
-        return node.textof()
+def set_boolean(dict, name, elem):
+    """
+    set_boolean(dict, name, elem) -> None
 
+    If element, 'name' is found in 'elem', set 'dict'['name'] to a boolean
+    from the 'yes' or 'no' content of the node
+    """
+    node = elem.find(name)
+    if node is not None:
+        if node.text.lower() == 'yes':
+            dict[name] = True
+        elif node.text.lower() == 'no':
+            dict[name] = False
 
-    #
-    # Magic
-    #
+def append_text(dict, name, elem, with_lang=True):
+    """
+    append_text(dict, name, elem, with_lang=True) -> None
 
-    def _call(self, node):
-        try:
-            return getattr(self, string.replace(node.name.encode(), '-', 
'_'))(node)
-        except NameError:
-            return '**Unhandled Element**'
-        except AttributeError:
-            return '**Unhandled Element**'
+    Append any text nodes with 'name' found in 'elem' to 'dict'['name']. If
+    'with_lang' is 'True', a tuple of ('text', 'lang') is appended
+    """
+    for node in elem.findall(name):
+        if not dict.has_key(name):
+            dict[name] = []
+        if with_lang:
+            dict[name].append((node.text, node.get('lang', '')))
+        else:
+            dict[name].append(node.text)
 
-class _ChannelHandler:
+def set_text(dict, name, elem, with_lang=True):
     """
-    Handles XMLTV channel elements
+    set_text(dict, name, elem, with_lang=True) -> None
+
+    Set 'dict'['name'] to the text found in 'name', if found under 'elem'. If
+    'with_lang' is 'True', a tuple of ('text', 'lang') is set
     """
-    def display_name(self, node):
-        return _readwithlang(node)
+    node = elem.find(name)
+    if node is not None:
+        if with_lang:
+            dict[name] = (node.text, node.get('lang', ''))
+        else:
+            dict[name] = node.text
 
-    def icon(self, node):
-        data = {}
-        for attr in (u'src', u'width', u'height'):
-            if node.attrs.has_key(('', attr)):
-                data[attr.encode(locale, 'ignore')] = _getxmlattr(node, attr)
-        return data
+def append_icons(dict, elem):
+    """
+    append_icons(dict, elem) -> None
 
-    def url(self, node):
-        return node.textof()
+    Append any icons found under 'elem' to 'dict'
+    """
+    for iconnode in elem.findall('icon'):
+        if not dict.has_key('icon'):
+            dict['icon'] = []
+        icond = {}
+        set_attrs(icond, iconnode, ('src', 'width', 'height'))
+        dict['icon'].append(icond)
 
 
-    #
-    # More Magic
-    #
+def elem_to_channel(elem):
+    """
+    elem_to_channel(Element) -> dict
 
-    def _call(self, node):
-        try:
-            return getattr(self, string.replace(node.name.encode(), '-', 
'_'))(node)
-        except NameError:
-            return '**Unhandled Element**'
+    Convert channel element to dictionary
+    """
+    d = {'id': elem.get('id'),
+         'display-name': []}
 
+    append_text(d, 'display-name', elem)
+    append_icons(d, elem)
+    append_text(d, 'url', elem, with_lang=False)
 
-#
-# Some convenience functions, treat them as private
-#
+    return d
 
-def _extractNodes(node, handler):
-    """
-    Builds a dictionary from the sub-elements of node.
-    'handler' should be an instance of a handler class
+def read_channels(fp=None, tree=None):
     """
-    result = {}
-    for child in node.children:
-        if not result.has_key(child.name):
-            result[child.name.encode(locale, 'ignore')] = []
-        result[child.name.encode(locale, 
'ignore')].append(handler._call(child))
-    return result
+    read_channels(fp=None, tree=None) -> list
 
-def _getxmlattr(node, attr):
+    Return a list of channel dictionaries from file object 'fp' or the
+    ElementTree 'tree'
     """
-    If 'attr' is present in 'node', return the value, else return an empty
-    string
+    if fp:
+        et = ElementTree()
+        tree = et.parse(fp)
+    return [elem_to_channel(elem) for elem in tree.findall('channel')]
 
-    Yeah, yeah, namespaces are ignored and all that stuff
-    """
-    if node.attrs.has_key((u'', attr)):
-        return node.attrs[(u'', attr)]
-    else:
-        return u''
 
-def _readwithlang(node):
-    """
-    Returns a tuple containing the text of a 'node' and the text of the 'lang'
-    attribute
+def elem_to_programme(elem):
     """
-    return (node.textof(), _getxmlattr(node, u'lang'))
+    elem_to_programme(Element) -> dict
 
-def _decodeboolean(node):
-    text = node.textof().lower()
-    if text == 'yes':
-        return 1
-    elif text == 'no':
-        return 0
-    else:
-        return None
-
-def _node_to_programme(node):
-    """
-    Create a programme dictionary from a qp_xml 'node'
+    Convert programme element to dictionary
     """
-    handler = _ProgrammeHandler()
-    programme = _extractNodes(node, handler)
+    d = {'start': elem.get('start'),
+         'channel': elem.get('channel'),
+         'title': []}
 
-    for attr in (u'start', u'channel'):
-        programme[attr.encode(locale, 'ignore')] = node.attrs[(u'', attr)]
-    if (u'', u'stop') in node.attrs:
-        programme[u'stop'.encode(locale, 'ignore')] = node.attrs[(u'', 
u'stop')]
-    #else:
-        # Sigh. Make show zero-length. This will allow the show to appear in
-        # searches, but it won't be seen in a grid, if the grid is drawn to
-        # scale
-        #programme[u'stop'.encode(locale, 'ignore')] = node.attrs[(u'', 
u'start')]
-    return programme
+    set_attrs(d, elem, ('stop', 'pdc-start', 'vps-start', 'showview',
+                        'videoplus', 'clumpidx'))
 
-def _node_to_channel(node):
-    """
-    Create a channel dictionary from a qp_xml 'node'
-    """
-    handler = _ChannelHandler()
-    channel = _extractNodes(node, handler)
+    append_text(d, 'title', elem)
+    append_text(d, 'sub-title', elem)
+    append_text(d, 'desc', elem)
 
-    channel['id'] = node.attrs[('', 'id')]
-    return channel
+    crednode = elem.find('credits')
+    if crednode is not None:
+        creddict = {}
+        for credtype in ('director', 'actor', 'writer', 'adapter', 'producer',
+                         'presenter', 'commentator', 'guest'):
+            append_text(creddict, credtype, crednode, with_lang=False)
+        d['credits'] = creddict
 
+    set_text(d, 'date', elem, with_lang=False)
+    append_text(d, 'category', elem)
+    set_text(d, 'language', elem)
+    set_text(d, 'orig-language', elem)
 
-def read_programmes(fp):
-    """
-    Read an XMLTV file and get out the relevant information for each
-    programme.
+    lennode = elem.find('length')
+    if lennode is not None:
+        lend = {'units': lennode.get('units'),
+                'length': lennode.text}
+        d['length'] = lend
 
-    Parameter: file object to read from
-    Returns: list of hashes with start, titles, etc.
-    """
-    parser = qp_xml.Parser()
-    doc = parser.parse(fp.read())
+    append_icons(d, elem)
+    append_text(d, 'url', elem, with_lang=False)
+    append_text(d, 'country', elem)
 
-    programmes = []
+    for epnumnode in elem.findall('episode-num'):
+        if not d.has_key('episode-num'):
+            d['episode-num'] = []
+        d['episode-num'].append((epnumnode.text,
+                                 epnumnode.get('system', 'xmltv_ns')))
 
-    for node in doc.children:
-        if node.name == u'programme':
-            programmes.append(_node_to_programme(node))
+    vidnode = elem.find('video')
+    if vidnode is not None:
+        vidd = {}
+        for name in ('present', 'colour'):
+            set_boolean(vidd, name, vidnode)
+        for videlem in ('aspect', 'quality'):
+            venode = vidnode.find(videlem)
+            if venode is not None:
+                vidd[videlem] = venode.text
+        d['video'] = vidd
 
-    return programmes
+    audnode = elem.find('audio')
+    if audnode is not None:
+        audd = {}
+        set_boolean(audd, 'present', audnode)
+        stereonode = audnode.find('stereo')
+        if stereonode is not None:
+            audd['stereo'] = stereonode.text
+        d['audio'] = audd
 
+    psnode = elem.find('previously-shown')
+    if psnode is not None:
+        psd = {}
+        set_attrs(psd, psnode, ('start', 'channel'))
+        d['previously-shown'] = psd
 
-def read_data(fp):
-    """
-    Get the source and other info from an XMLTV file.
+    set_text(d, 'premiere', elem)
+    set_text(d, 'last-chance', elem)
 
-    Parameter: filename to read from
-    Returns: dictionary of <tv> attributes
-    """
-    parser = qp_xml.Parser()
-    doc = parser.parse(fp.read())
+    if elem.find('new') is not None:
+        d['new'] = True
 
-    attrs = {}
+    for stnode in elem.findall('subtitles'):
+        if not d.has_key('subtitles'):
+            d['subtitles'] = []
+        std = {}
+        set_attrs(std, stnode, ('type',))
+        set_text(std, 'language', stnode)
+        d['subtitles'].append(std)
 
-    for key in doc.attrs.keys():
-        attrs[key[1].encode(locale, 'ignore')] = doc.attrs[key]
+    for ratnode in elem.findall('rating'):
+        if not d.has_key('rating'):
+            d['rating'] = []
+        ratd = {}
+        set_attrs(ratd, ratnode, ('system',))
+        set_text(ratd, 'value', ratnode, with_lang=False)
+        append_icons(ratd, ratnode)
+        d['rating'].append(ratd)
 
-    return attrs
+    srnode = elem.find('star-rating')
+    if srnode is not None:
+        srd = {}
+        set_text(srd, 'value', srnode, with_lang=False)
+        append_icons(srd, srnode)
+        d['star-rating'] = srd
 
+    return d
 
-def read_channels(fp):
+def read_programmes(fp=None, tree=None):
     """
-    Read the channels.xml file and return a list of channel
-    information.
+    read_programmes(fp=None, tree=None) -> list
+
+    Return a list of programme dictionaries from file object 'fp' or the
+    ElementTree 'tree'
     """
-    parser = qp_xml.Parser()
-    doc = parser.parse(fp.read())
+    if fp:
+        et = ElementTree()
+        tree = et.parse(fp)
+    return [elem_to_programme(elem) for elem in tree.findall('programme')]
+
 
-    channels = []
+def read_data(fp=None, tree=None):
+    """
+    read_data(fp=None, tree=None) -> dict
 
-    for node in doc.children:
-        if node.name == u'channel':
-            channels.append(_node_to_channel(node))
+    Get the source and other info from file object fp or the ElementTree
+    'tree'
+    """
+    if fp:
+        et = ElementTree()
+        tree = et.parse(fp)
 
-    return channels
+    d = {}
+    set_attrs(d, tree, ('date', 'source-info-url', 'source-info-name',
+                        'source-data-url', 'generator-info-name',
+                        'generator-info-url'))
+    return d
 
 
 
@@ -418,16 +272,14 @@
     **All strings passed to this class must be Unicode, except for dictionary
     keys**
     """
-    def __init__(self, fp, encoding="iso-8859-1", date=None,
+    def __init__(self, encoding="utf-8", date=None,
                  source_info_url=None, source_info_name=None,
                  generator_info_url=None, generator_info_name=None):
         """
         Arguments:
 
-          'fp' -- A File object to write XMLTV data to
-
           'encoding' -- The text encoding that will be used.
-                        *Defaults to 'iso-8859-1'*
+                        *Defaults to 'utf-8'*
 
           'date' -- The date this data was generated. *Optional*
 
@@ -445,297 +297,270 @@
                                    'generator_info_url'. *Optional*
 
         """
-        self.fp = fp
         self.encoding = encoding
-        self.date = date
-        self.source_info_url = source_info_url
-        self.source_info_name = source_info_name
-        self.generator_info_url = generator_info_url
-        self.generator_info_name = generator_info_name
-
-        s = """<?xml version="1.0" encoding="%s"?>
-<!DOCTYPE tv SYSTEM "xmltv.dtd">
-""" % self.encoding
-
-        # tv start tag
-        s += "<tv"
-        for attr in ('date', 'source_info_url', 'source_info_name',
-                     'generator_info_url', 'generator_info_name'):
-            if attr:
-                s += ' %s="%s"' % (attr, self.__dict__[attr])
-        s += ">\n"
-        self.fp.write(s)
+        self.data = {'date': date,
+                     'source_info_url': source_info_url,
+                     'source_info_name': source_info_name,
+                     'generator_info_url': generator_info_url,
+                     'generator_info_name': generator_info_name}
 
+        self.root = Element('tv')
+        for attr in self.data.keys():
+            if self.data[attr]:
+                self.root.set(attr, self.data[attr])
 
-    def _validateStructure(self, d):
+    def setattr(self, node, attr, value):
         """
-        Raises 'TypeError' if any strings are not Unicode
+        setattr(node, attr, value) -> None
 
-        Argumets:
-
-          's' -- A dictionary
+        Set 'attr' in 'node' to 'value'
         """
-        if type(d) == types.StringType:
-            raise TypeError ('All strings, except keys, must be in Unicode. 
Bad string: %s' % d)
-        elif type(d) == types.DictType:
-            for key in d.keys():
-                self._validateStructure(d[key])
-        elif type(d) == types.TupleType or type(d) == types.ListType:
-            for i in d:
-                self._validateStructure(i)
-
+        node.set(attr, value.encode(self.encoding))
 
-    def _formatCDATA(self, cdata):
+    def settext(self, node, text, with_lang=True):
         """
-        Returns fixed and encoded CDATA
-
-        Arguments:
+        settext(node, text) -> None
 
-          'cdata' -- CDATA you wish to encode
+        Set 'node's text content to 'text'
         """
-        # Let's do what 4Suite does, and replace bad characters with '?'
-        cdata = XML_BADCHARS.sub(u'?', cdata)
-        return saxutils.escape(cdata).encode(self.encoding)
-
+        if with_lang:
+            if text[0] == None:
+                node.text = None
+            else:
+                node.text = text[0].encode(self.encoding)
+            if text[1]:
+                node.set('lang', text[1].encode(self.encoding))
+        else:
+            if text == None:
+                node.text = None
+            else:
+                node.text = text.encode(self.encoding)
 
-    def _formatTag(self, tagname, attrs=None, pcdata=None, indent=4):
+    def seticons(self, node, icons):
         """
-        Return a simple tag
-
-        Arguments:
-
-          'tagname' -- Name of tag
+        seticon(node, icons) -> None
 
-          'attrs' -- dictionary of attributes
+        Create 'icons' under 'node'
+        """
+        for icon in icons:
+            if not icon.has_key('src'):
+                raise ValueError("'icon' element requires 'src' attribute")
+            i = SubElement(node, 'icon')
+            for attr in ('src', 'width', 'height'):
+                if icon.has_key(attr):
+                    self.setattr(i, attr, icon[attr])
 
-          'pcdata' -- Content
 
-          'indent' -- Number of spaces to indent
+    def set_zero_ormore(self, programme, element, p):
         """
-        s = indent*' '
-        s += '<%s' % tagname
-        if attrs:
-            for key in attrs.keys():
-                s += ' %s="%s"' % (key, self._formatCDATA(attrs[key]))
-        if pcdata:
-            s += '>%s</%s>\n' % (self._formatCDATA(pcdata), tagname)
-        else:
-            s += '/>\n'
-        return s
+        set_zero_ormore(programme, element, p) -> None
 
+        Add nodes under p for the element 'element', which occurs zero
+        or more times with PCDATA and a 'lang' attribute
+        """
+        if programme.has_key(element):
+            for item in programme[element]:
+                e = SubElement(p, element)
+                self.settext(e, item)
 
-    def end(self):
+    def set_zero_orone(self, programme, element, p):
         """
-        Write the end of an XMLTV document
+        set_zero_ormore(programme, element, p) -> None
+
+        Add nodes under p for the element 'element', which occurs zero
+        times or once with PCDATA and a 'lang' attribute
         """
-        self.fp.write("</tv>\n")
+        if programme.has_key(element):
+            e = SubElement(p, element)
+            self.settext(e, programme[element])
 
 
-    def write_programme(self, programme):
+    def addProgramme(self, programme):
         """
-        Write a single XMLTV 'programme'
+        Add a single XMLTV 'programme'
 
         Arguments:
 
           'programme' -- A dict representing XMLTV data
         """
-        self._validateStructure(programme)
-        s = '  <programme'
+        p = SubElement(self.root, 'programme')
 
         # programme attributes
         for attr in ('start', 'channel'):
             if programme.has_key(attr):
-                s += ' %s="%s"' % (attr, self._formatCDATA(programme[attr]))
+                self.setattr(p, attr, programme[attr])
             else:
                 raise ValueError("'programme' must contain '%s' attribute" % 
attr)
 
         for attr in ('stop', 'pdc-start', 'vps-start', 'showview', 
'videoplus', 'clumpidx'):
             if programme.has_key(attr):
-                s += ' %s="%s"' % (attr, self._formatCDATA(programme[attr]))
-
-        s += '>\n'
-
-        # Required children
-        err = 0
-        if programme.has_key('title'):
-            if len(programme['title']) > 0:
-                for title in programme['title']:
-                    if title[1] != u'':
-                        attrs = {'lang': title[1]}
-                    else:
-                        attrs=None
-                    s += self._formatTag('title', attrs, title[0])
-            else:
-                err = 1
-        else:
-            err = 1
-
-        if err:
-            raise ValueError("'programme' must contain at least one 'title' 
element")
-
-        # Zero or more children with PCDATA and 'lang' attribute
-        for element in ('sub-title', 'desc', 'category', 'country'):
-            if programme.has_key(element):
-                for item in programme[element]:
-                    if item[1] != u'':
-                        attrs = {'lang': item[1]}
-                    else:
-                        attrs=None
-                    s += self._formatTag(element, attrs, item[0])
+                self.setattr(p, attr, programme[attr])
 
-        # Zero or one children with PCDATA and 'lang' attribute
-        for element in ('language', 'orig-language', 'premiere', 
'last-chance'):
-            if programme.has_key(element):
-                if len(programme[element]) != 1:
-                    raise ValueError("Only one '%s' element allowed" % element)
-                if programme[element][0][1] != u'':
-                    attrs = {'lang': programme[element][0][1]}
-                else:
-                    attrs=None
-                s += self._formatTag(element, attrs, programme[element][0][0])
+        for title in programme['title']:
+            t = SubElement(p, 'title')
+            self.settext(t, title)
+
+        # Sub-title and description
+        for element in ('sub-title', 'desc'):
+            self.set_zero_ormore(programme, element, p)
 
         # Credits
         if programme.has_key('credits'):
-            s += '    <credits>\n'
-            for credit in ('director', 'actor', 'writer', 'adapter',
-                           'producer', 'presenter', 'commentator', 'guest'):
-                if programme['credits'][0].has_key(credit):
-                    for name in programme['credits'][0][credit]:
-                        s += self._formatTag(credit, pcdata=name, indent=6)
-            s += '    </credits>\n'
+            c = SubElement(p, 'credits')
+            for credtype in ('director', 'actor', 'writer', 'adapter',
+                             'producer', 'presenter', 'commentator', 'guest'):
+                if programme['credits'].has_key(credtype):
+                    for name in programme['credits'][credtype]:
+                        cred = SubElement(c, credtype)
+                        self.settext(cred, name, with_lang=False)
 
         # Date
         if programme.has_key('date'):
-            if len(programme['date']) != 1:
-                raise ValueError("Only one 'date' element allowed")
-            s += self._formatTag('date', pcdata=programme['date'][0])
+            d = SubElement(p, 'date')
+            self.settext(d, programme['date'], with_lang=False)
+
+        # Category
+        self.set_zero_ormore(programme, 'category', p)
+
+        # Language and original language
+        for element in ('language', 'orig-language'):
+            self.set_zero_orone(programme, element, p)
 
         # Length
         if programme.has_key('length'):
-            if len(programme['length']) != 1:
-                raise ValueError("Only one 'length' element allowed")
-            s += self._formatTag('length', {'units': 
programme['length'][0]['units']}, 
str(programme['length'][0]['length']).decode(self.encoding))
+            l = SubElement(p, 'length')
+            self.setattr(l, 'units', programme['length']['units'])
+            self.settext(l, programme['length']['length'], with_lang=False)
 
         # Icon
         if programme.has_key('icon'):
-            for icon in programme['icon']:
-                if icon.has_key('src'):
-                    s += self._formatTag('icon', icon)
-                else:
-                    raise ValueError("'icon' element requires 'src' attribute")
+            self.seticons(p, programme['icon'])
 
         # URL
         if programme.has_key('url'):
             for url in programme['url']:
-                s += self._formatTag('url', pcdata=url)
+                u = SubElement(p, 'url')
+                self.settext(u, url, with_lang=False)
+
+        # Country
+        self.set_zero_ormore(programme, 'country', p)
 
         # Episode-num
         if programme.has_key('episode-num'):
-            if len(programme['episode-num']) != 1:
-                raise ValueError("Only one 'episode-num' element allowed")
-            s += self._formatTag('episode-num', {'system': 
programme['episode-num'][0][1]},
-                                programme['episode-num'][0][0])
-
-        # Video and audio details
-        for element in ('video', 'audio'):
-            if programme.has_key(element):
-                s += '    <%s>\n' % element
-                for key in programme[element][0]:
-                    s += self._formatTag(key, 
pcdata=str(programme[element][0][key]).decode(self.encoding), indent=6)
-                s += '    </%s>\n' % element
+            for epnum in programme['episode-num']:
+                e = SubElement(p, 'episode-num')
+                self.setattr(e, 'system', epnum[1])
+                self.settext(e, epnum[0], with_lang=False)
+
+        # Video details
+        if programme.has_key('video'):
+            e = SubElement(p, 'video')
+            for videlem in ('aspect', 'quality'):
+                if programme['video'].has_key(videlem):
+                    v = SubElement(e, videlem)
+                    self.settext(v, programme['video'][videlem], 
with_lang=False)
+            for attr in ('present', 'colour'):
+                if programme['video'].has_key(attr):
+                    a = SubElement(e, attr)
+                    if programme['video'][attr]:
+                        self.settext(a, 'yes', with_lang=False)
+                    else:
+                        self.settext(a, 'no', with_lang=False)
+
+        # Audio details
+        if programme.has_key('audio'):
+            a = SubElement(p, 'audio')
+            if programme['audio'].has_key('stereo'):
+                s = SubElement(a, 'stereo')
+                self.settext(s, programme['audio']['stereo'], with_lang=False)
+            if programme['audio'].has_key('present'):
+                p = SubElement(a, 'present')
+                if programme['audio']['present']:
+                    self.settext(p, 'yes', with_lang=False)
+                else:
+                    self.settext(p, 'no', with_lang=False)
 
         # Previously shown
         if programme.has_key('previously-shown'):
-            s += self._formatTag('previously-shown', 
programme['previously-shown'][0])
+            ps = SubElement(p, 'previously-shown')
+            for attr in ('start', 'channel'):
+                if programme['previously-shown'].has_key(attr):
+                    self.setattr(ps, attr, programme['previously-shown'][attr])
+
+        # Premiere / last chance
+        for element in ('premiere', 'last-chance'):
+            self.set_zero_orone(programme, element, p)
 
         # New
         if programme.has_key('new'):
-            s += self._formatTag('new')
+            n = SubElement(p, 'new')
 
         # Subtitles
         if programme.has_key('subtitles'):
-            s += '    <subtitles'
-            if programme['subtitles'][0].has_key('type'):
-                s += ' type="%s"' % 
self._formatCDATA(programme['subtitles'][0]['type'])
-            s += '>\n'
-            if programme['subtitles'][0].has_key('language'):
-                if programme['subtitles'][0]['language'][1] != u'':
-                    attrs = {'lang': programme['subtitles'][0]['language'][1]}
-                else:
-                    attrs = None
-                s += self._formatTag('language', None, 
programme['subtitles'][0]['language'][0], indent=6)
-            s += '    </subtitles>\n'
-
-        # Rating and star rating
-        for element in ('rating', 'star-rating'):
-            if programme.has_key(element):
-                s += '    <%s' % element
-                if element == 'rating':
-                    if programme[element][0].has_key('system'):
-                        s += ' system="%s"' % 
self._formatCDATA(programme[element][0]['system'])
-                s += '>\n'
-                if programme[element][0].has_key('value'):
-                    s += self._formatTag('value', 
pcdata=programme[element][0]['value'], indent=6)
-                if programme[element][0].has_key('icon'):
-                    for icon in programme[element][0]['icon']:
-                        s += self._formatTag('icon', icon, indent=6)
-                s += '    </%s>\n' % element
-
-        # End tag
-        s += '  </programme>\n'
-
-        self.fp.write(s)
+            for subtitles in programme['subtitles']:
+                s = SubElement(p, 'subtitles')
+                if subtitles.has_key('type'):
+                    self.setattr(s, 'type', subtitles['type'])
+                if subtitles.has_key('language'):
+                    l = SubElement(s, 'language')
+                    self.settext(l, subtitles['language'])
+
+        # Rating
+        if programme.has_key('rating'):
+            for rating in programme['rating']:
+                r = SubElement(p, 'rating')
+                if rating.has_key('system'):
+                    self.setattr(r, 'system', rating['system'])
+                v = SubElement(r, 'value')
+                self.settext(v, rating['value'], with_lang=False)
+                if rating.has_key('icon'):
+                    self.seticons(r, rating['icon'])
+
+        # Star rating
+        if programme.has_key('star-rating'):
+            sr = SubElement(p, 'star-rating')
+            v = SubElement(sr, 'value')
+            self.settext(v, programme['star-rating']['value'], with_lang=False)
+            if programme['star-rating'].has_key('icon'):
+                self.seticons(sr, programme['star-rating']['icon'])
 
-
-    def write_channel(self, channel):
+    def addChannel(self, channel):
         """
-        Write a single XMLTV 'channel'
+        add a single XMLTV 'channel'
 
         Arguments:
 
           'channel' -- A dict representing XMLTV data
         """
-        self._validateStructure(channel)
-        s = '  <channel id="%s">\n' % channel['id']
+        c = SubElement(self.root, 'channel')
+        self.setattr(c, 'id', channel['id'])
 
-        # Write display-name(s)
-        err = 0
-        if channel.has_key('display-name'):
-            if len(channel['display-name']) > 0:
-                for name in channel['display-name']:
-                    if name[1] != u'':
-                        attrs = {'lang': name[1]}
-                    else:
-                        attrs = None
-                    s += self._formatTag('display-name', attrs, name[0])
-            else:
-                err = 1
-        else:
-            err = 1
-
-        if err:
-            raise ValueError("'channel' must contain at least one 
'display-name' element")
+        # Display Name
+        for display_name in channel['display-name']:
+            dn = SubElement(c, 'display-name')
+            self.settext(dn, display_name)
 
         # Icon
         if channel.has_key('icon'):
-            for icon in channel['icon']:
-                if icon.has_key('src'):
-                    s += self._formatTag('icon', icon)
-                else:
-                    raise ValueError("'icon' element requires 'src' attribute")
+            self.seticons(c, channel['icon'])
 
         # URL
         if channel.has_key('url'):
             for url in channel['url']:
-                s += self._formatTag('url', pcdata=url)
+                u = SubElement(c, 'url')
+                self.settext(u, url, with_lang=False)
 
-        s += '  </channel>\n'
-
-        self.fp.write(s)
+    def write(self, file):
+        """
+        write(file) -> None
 
+        Write XML to filename of file object in 'file'
+        """
+        et = ElementTree(self.root)
+        et.write(file)
 
 if __name__ == '__main__':
-    # Tests
+# Tests
     from pprint import pprint
     from StringIO import StringIO
     import sys
@@ -779,9 +604,10 @@
     <length units="minutes">22</length>
     <episode-num system="xmltv_ns">7 . 1 . 1/1</episode-num>
     <video>
-      <colour>1</colour>
-      <present>1</present>
+      <colour>yes</colour>
+      <present>yes</present>
       <aspect>4:3</aspect>
+      <quality>standard</quality>
     </video>
     <audio>
       <stereo>stereo</stereo>
@@ -799,6 +625,7 @@
       <value>4/5</value>
       <icon src="http://some.star/icon.png"; width="32" height="32"/>
     </star-rating>
+    <url>http://www.nbc.com</url>
   </programme>
 </tv>
 """)
@@ -809,45 +636,47 @@
     pprint(read_programmes(xmldata))
 
     # Test the writer
-    programmes = [{'audio': [{'stereo': u'stereo'}],
+    programmes = [{'audio': {'stereo': u'stereo'},
                    'category': [(u'Biz', u''), (u'Fin', u'')],
                    'channel': u'C23robtv.zap2it.com',
-                   'date': [u'2003'],
+                   'date': u'2003',
                    'start': u'20030702000000 ADT',
                    'stop': u'20030702003000 ADT',
                    'title': [(u'This Week in Business', u'')]},
-                  {'audio': [{'stereo': u'stereo'}],
+                  {'audio': {'stereo': u'stereo'},
                    'category': [(u'Comedy', u'')],
                    'channel': u'C36wuhf.zap2it.com',
                    'country': [(u'USA', u'')],
-                   'credits': [{'producer': [u'Larry David'], 'actor': 
[u'Jerry Seinfeld']}],
-                   'date': [u'1995'],
+                   'credits': {'producer': [u'Larry David'], 'actor': [u'Jerry 
Seinfeld']},
+                   'date': u'1995',
                    'desc': [(u'In an effort to grow up, George proposes 
marriage to former girlfriend Susan.',
                              u'')],
-                   'episode-num': [(u'7 . 1 . 1/1', u'xmltv_ns')],
-                   'language': [(u'English', u'')],
-                   'last-chance': [(u'Hah!', u'')],
-                   'length': [{'units': u'minutes', 'length': 22}],
-                   'new': [1],
-                   'orig-language': [(u'English', u'')],
-                   'premiere': [(u'Not really. Just testing', u'en')],
-                   'previously-shown': [{'channel': u'C12whdh.zap2it.com',
-                                         'start': u'19950921103000 ADT'}],
+                   'episode-num': (u'7 . 1 . 1/1', u'xmltv_ns'),
+                   'language': (u'English', u''),
+                   'last-chance': (u'Hah!', u''),
+                   'length': {'units': u'minutes', 'length': '22'},
+                   'new': True,
+                   'orig-language': (u'English', u''),
+                   'premiere': (u'Not really. Just testing', u'en'),
+                   'previously-shown': {'channel': u'C12whdh.zap2it.com',
+                                        'start': u'19950921103000 ADT'},
                    'rating': [{'icon': [{'height': u'64',
                                          'src': 
u'http://some.ratings/PGicon.png',
                                          'width': u'64'}],
                                'system': u'VCHIP',
                                'value': u'PG'}],
-                   'star-rating': [{'icon': [{'height': u'32',
-                                              'src': 
u'http://some.star/icon.png',
-                                              'width': u'32'}],
-                                    'value': u'4/5'}],
+                   'star-rating': {'icon': [{'height': u'32',
+                                             'src': 
u'http://some.star/icon.png',
+                                             'width': u'32'}],
+                                   'value': u'4/5'},
                    'start': u'20030702000000 ADT',
                    'stop': u'20030702003000 ADT',
                    'sub-title': [(u'The Engagement', u'')],
                    'subtitles': [{'type': u'teletext', 'language': 
(u'English', u'')}],
                    'title': [(u'Seinfeld', u'')],
-                   'video': [{'colour': 1, 'aspect': u'4:3', 'present': 1}]}]
+                   'url': [(u'http://www.nbc.com/')],
+                   'video': {'colour': True, 'aspect': u'4:3', 'present': True,
+                             'quality': 'standard'}}]
 
     channels = [{'display-name': [(u'Channel 10 ELTV', u'')],
                  'id': u'C10eltv.zap2it.com',
@@ -857,14 +686,14 @@
                  'id': u'C11cbht.zap2it.com'}]
 
 
-    w = Writer(sys.stdout, encoding="iso-8859-1",
+    w = Writer(encoding="iso-8859-1",
                date="20030811003608 -0300",
                source_info_url="http://www.funktronics.ca/python-xmltv";,
                source_info_name="Funktronics",
                generator_info_name="python-xmltv",
                generator_info_url="http://www.funktronics.ca/python-xmltv";)
     for c in channels:
-        w.write_channel(c)
+        w.addChannel(c)
     for p in programmes:
-        w.write_programme(p)
-    w.end()
+        w.addProgramme(p)
+    w.write(sys.stdout)

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to