~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to lib/xhtmltools.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
17
#
 
18
# In addition, as a special exception, the copyright holders give
 
19
# permission to link the code of portions of this program with the OpenSSL
 
20
# library.
 
21
#
 
22
# You must obey the GNU General Public License in all respects for all of
 
23
# the code used other than OpenSSL. If you modify file(s) with this
 
24
# exception, you may extend this exception to your version of the file(s),
 
25
# but you are not obligated to do so. If you do not wish to do so, delete
 
26
# this exception statement from your version. If you delete this exception
 
27
# statement from all source files in the program, then also delete it here.
 
28
 
 
29
"""``miro.xhtmltools`` -- XML related utility functions.
 
30
"""
 
31
 
 
32
import xml.sax.saxutils
 
33
import xml.dom
 
34
import re
 
35
from urllib import quote, quote_plus, unquote
 
36
from HTMLParser import HTMLParser, HTMLParseError
 
37
import random
 
38
import logging
 
39
 
 
40
class XHTMLifier(HTMLParser):
 
41
    """Very simple parser to convert HTML to XHTML
 
42
 
 
43
    """
 
44
    # FIXME - this should probably be rewritten to use StringIO.
 
45
    def convert(self, data, add_top_tags=False, filter_font_tags=False):
 
46
        """Converts an HTML data unicode string to an XHTML data
 
47
        unicode string.
 
48
        """
 
49
        try:
 
50
            if add_top_tags:
 
51
                self.output = u'<html><head></head><body>'
 
52
            else:
 
53
                self.output = ''
 
54
            self.stack = []
 
55
            self.filter_font_tags = filter_font_tags
 
56
            self.feed(data)
 
57
            self.close()
 
58
            while len(self.stack) > 0:
 
59
                temp = self.stack.pop()
 
60
                self.output += u'</'+temp+'>'
 
61
            if add_top_tags:
 
62
                self.output += u'</body></html>'
 
63
            return self.output
 
64
 
 
65
        except HTMLParseError:
 
66
            logging.warn("xhtmlifier: parse exception")
 
67
            logging.debug("data: '%s'", data)
 
68
 
 
69
    def handle_starttag(self, tag, attrs):
 
70
        if tag.lower() == 'br':
 
71
            self.output += u'<br/>'
 
72
            return
 
73
 
 
74
 
 
75
        if not (tag.lower() == 'font' and self.filter_font_tags):
 
76
            self.output += u'<' + tag
 
77
            for attr in attrs:
 
78
                if attr[1] == None:
 
79
                    self.output += (u' ' +
 
80
                                    attr[0] +
 
81
                                    u'=' +
 
82
                                    xml.sax.saxutils.quoteattr(attr[0]))
 
83
                else:
 
84
                    self.output += (u' ' +
 
85
                                    attr[0] +
 
86
                                    u'=' +
 
87
                                    xml.sax.saxutils.quoteattr(attr[1]))
 
88
            self.output += u'>'
 
89
        self.stack.append(tag)
 
90
 
 
91
    def handle_endtag(self, tag):
 
92
        if tag.lower() != 'br' and len(self.stack) > 1:
 
93
            temp = self.stack.pop()
 
94
            if not (tag.lower() == 'font' and self.filter_font_tags):
 
95
                self.output += u'</'+temp+u'>'
 
96
                while temp != tag and len(self.stack) > 1:
 
97
                    temp = self.stack.pop()
 
98
                    self.output += u'</' + temp + u'>'
 
99
 
 
100
    def handle_startendtag(self, tag, attrs):
 
101
        self.output += u'<' + tag + u'/>'
 
102
 
 
103
    def handle_data(self, data):
 
104
        data = data.replace(u'&', u'&amp;')
 
105
        data = data.replace(u'<', u'&lt;')
 
106
        self.output += data
 
107
 
 
108
    def handle_charref(self, name):
 
109
        self.output += u'&#' + name + ';'
 
110
 
 
111
    def handle_entityref(self, name):
 
112
        self.output += u'&' + name + ';'
 
113
 
 
114
def unescape(data):
 
115
    """Parses HTML entities in data"""
 
116
    return xml.sax.saxutils.unescape(data)
 
117
 
 
118
def urlencode(data):
 
119
    """Encodes string for use in a URL"""
 
120
    if isinstance(data, unicode):
 
121
        data = data.encode('utf-8', 'replace')
 
122
    else:
 
123
        data = str(data)
 
124
    return unicode(quote(data))
 
125
 
 
126
def xhtmlify(data, add_top_tags=False, filter_font_tags=False):
 
127
    """Returns XHTMLified version of HTML document"""
 
128
    x = XHTMLifier()
 
129
    ret = x.convert(data, add_top_tags, filter_font_tags)
 
130
 
 
131
    # if we got a bad return, try it again without filtering font
 
132
    # tags
 
133
    if ret is None and filter_font_tags:
 
134
        x = XHTMLifier()
 
135
        ret = x.convert(data, add_top_tags, filter_font_tags=False)
 
136
 
 
137
    # if that's still bad, try converting &quot; to ".
 
138
    # this fixes bug #10095 where Google Video items are sometimes
 
139
    # half quoted.
 
140
    if ret is None:
 
141
        x = XHTMLifier()
 
142
        ret = x.convert(data.replace("&quot;", '"'), add_top_tags,
 
143
                        filter_font_tags=False)
 
144
 
 
145
    if ret is None:
 
146
        ret = u""
 
147
 
 
148
    return ret
 
149
 
 
150
XML_HEADER_RE = re.compile("^\<\?xml\s*(.*?)\s*\?\>(.*)", re.S)
 
151
 
 
152
def fix_xml_header(data, charset):
 
153
    """Adds a <?xml ?> header to the given xml data or replaces an
 
154
    existing one without a charset with one that has a charset
 
155
    """
 
156
    header = XML_HEADER_RE.match(data)
 
157
    if header is None:
 
158
        # print "Adding header %s" % charset
 
159
        return '<?xml version="1.0" encoding="%s"?>%s' % (charset, data)
 
160
 
 
161
    xml_decl = header.expand('\\1')
 
162
    the_rest = header.expand('\\2')
 
163
    if xml_decl.find('encoding') != -1:
 
164
        return data
 
165
 
 
166
    # print "Changing header to include charset"
 
167
    return '<?xml %s encoding="%s"?>%s' % (xml_decl, charset, the_rest)
 
168
 
 
169
 
 
170
HTML_HEADER_RE = re.compile(
 
171
    u"^(.*)\<\s*head\s*(.*?)\s*\>(.*?)\</\s*head\s*\>(.*)", re.I | re.S)
 
172
 
 
173
def fix_html_header(data, charset):
 
174
    """Adds a <meta http-equiv="Content-Type" content="text/html;
 
175
    charset=blah"> tag to an HTML document
 
176
 
 
177
    Since we're only feeding this to our own HTML Parser anyway, we
 
178
    don't care that it might bung up XHTML.
 
179
    """
 
180
    header = HTML_HEADER_RE.match(data)
 
181
    if header is None:
 
182
        # something is very wrong with this HTML
 
183
        return data
 
184
 
 
185
    head_tags = header.expand('\\3')
 
186
    # this isn't exactly robust, but neither is scraping HTML
 
187
    if head_tags.lower().find('content-type') != -1:
 
188
        return data
 
189
 
 
190
    return header.expand('\\1<head\\2><meta http-equiv="Content-Type" content="text/html; charset=') + charset + header.expand('">\\3</head>\\4')
 
191
 
 
192
def url_encode_dict(orig):
 
193
    """Converts a Python dictionary to data suitable for a POST or GET
 
194
    submission
 
195
    """
 
196
    output = []
 
197
    for key, val in orig.items():
 
198
        if isinstance(val, list) or isinstance(val, tuple):
 
199
            for v in val:
 
200
                output.append('%s=%s' % (quote_plus(key), quote_plus(v)))
 
201
        elif isinstance(val, basestring):
 
202
            output.append('%s=%s' % (quote_plus(key), quote_plus(orig[key])))
 
203
        else:
 
204
            logging.warning("url_encode_dict: trying to encode non-string: '%s'", repr(val))
 
205
    return '&'.join(output)
 
206
 
 
207
def multipart_encode(post_vars, files):
 
208
    # Generate a random 64bit number for our boundaries
 
209
    boundary = 'dp%s' % hex(random.getrandbits(64))[2:-1]
 
210
    output = []
 
211
    if post_vars is not None:
 
212
        for key, value in post_vars.items():
 
213
            output.append('--%s\r\n' % boundary)
 
214
            output.append('Content-Disposition: form-data; name="%s"\r\n\r\n' %
 
215
                          quote_plus(key))
 
216
            if isinstance(value, unicode):
 
217
                value = value.encode('utf8', 'xmlcharrefreplace')
 
218
            output.append(value)
 
219
            output.append('\r\n')
 
220
    if files is not None:
 
221
        for key in files.keys():
 
222
            output.append('--%s\r\n' % boundary)
 
223
            output.append('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' %
 
224
                          (quote_plus(key),
 
225
                           quote_plus(files[key]['filename'])))
 
226
            output.append('Content-Type: %s\r\n\r\n' % files[key]['mimetype'])
 
227
 
 
228
            output.append(files[key]['handle'].read())
 
229
            output.append('\r\n')
 
230
            files[key]['handle'].close()
 
231
    output.append('--%s--\n' % boundary)
 
232
    return (''.join(output), boundary)