diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py index 16b38fe71..6b78828ef 100644 --- a/src/common/rst_xhtml_generator.py +++ b/src/common/rst_xhtml_generator.py @@ -21,39 +21,70 @@ try: from docutils import nodes,utils from docutils.parsers.rst.roles import set_classes except: + print "Requires docutils 0.4 for set_classes to be available" def create_xhtml(text): return None else: - def jep_reference_role(role, rawtext, text, lineno, inliner, - options={}, content=[]): - '''Role to make handy references to Jabber Enhancement Proposals (JEP). + def pos_int_validator(text): + """Validates that text can be evaluated as a positive integer.""" + result = int(text) + if result < 0: + raise ValueError("Error: value '%(text)s' " + "must be a positive integer") + return result - Use as :JEP:`71` (or jep, or jep-reference). - Modeled after the sample in docutils documentation. + def generate_uri_role( role_name, aliases, + anchor_text, base_url, + interpret_url, validator): + '''Creates and register a uri based "interpreted role". + + Those are similar to the RFC, and PEP ones, and take + role_name: + name that will be registered + aliases: + list of alternate names + anchor_text: + text that will be used, together with the role + base_url: + base url for the link + interpret_url: + this, modulo the validated text, will be added to it + validator: + should return the validated text, or raise ValueError ''' + def uri_reference_role(role, rawtext, text, lineno, inliner, + options={}, content=[]): + try: + valid_text = validator(text) + except ValueError, e: + msg = inliner.reporter.error( e.message % dict(text=text), line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + ref = base_url + interpret_url % valid_text + set_classes(options) + node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref, + **options) + return [node], [] - jep_base_url = 'http://www.jabber.org/jeps/' - jep_url = 'jep-%04d.html' - try: - jepnum = int(text) - if jepnum <= 0: - raise ValueError - except ValueError: - msg = inliner.reporter.error( - 'JEP number must be a number greater than or equal to 1; ' - '"%s" is invalid.' % text, line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - ref = jep_base_url + jep_url % jepnum - set_classes(options) - node = nodes.reference(rawtext, 'JEP ' + utils.unescape(text), refuri=ref, - **options) - return [node], [] + uri_reference_role.__doc__ = """Role to make handy references to URIs. - roles.register_canonical_role('jep-reference', jep_reference_role) - from docutils.parsers.rst.languages.en import roles - roles['jep-reference'] = 'jep-reference' - roles['jep'] = 'jep-reference' + Use as :%(role_name)s:`71` (or any of %(aliases)s). + It will use %(base_url)s+%(interpret_url)s + validator should throw a ValueError, containing optionally + a %%(text)s format, if the interpreted text is not valid. + """ % locals() + roles.register_canonical_role(role_name, uri_reference_role) + from docutils.parsers.rst.languages import en + en.roles[role_name] = role_name + for alias in aliases: + en.roles[alias] = role_name + + generate_uri_role('xep-reference', ('jep', 'xep'), + 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html', + pos_int_validator) + generate_uri_role('gajim-ticket-reference', ('ticket','gtrack'), + 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d', + pos_int_validator) class HTMLGenerator: '''Really simple HTMLGenerator starting from publish_parts. @@ -108,7 +139,7 @@ else: if __name__ == '__main__': - print Generator.create_xhtml(''' + print "test 1\n", Generator.create_xhtml(""" test:: >>> print 1 @@ -118,9 +149,10 @@ test:: this `` should trigger`` should trigger the problem. -''') - print Generator.create_xhtml(''' +""") + print "test 2\n", Generator.create_xhtml(""" *test1 test2_ -''') +""") + print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""") diff --git a/src/htmltextview.py b/src/htmltextview.py index acb2a9a00..75fbccf0a 100644 --- a/src/htmltextview.py +++ b/src/htmltextview.py @@ -40,9 +40,9 @@ import time import urllib2 import operator +if __name__ == '__main__': + from common import i18n from common import gajim -#from common import i18n - import tooltips @@ -56,13 +56,13 @@ allwhitespace_rx = re.compile('^\\s*$') display_resolution = 0.3514598*(gtk.gdk.screen_height() / float(gtk.gdk.screen_height_mm())) -#embryo of CSS classes +# embryo of CSS classes classes = { #'system-message':';display: none', 'problematic':';color: red', } -#styles for elemens +# styles for elements element_styles = { 'u' : ';text-decoration: underline', 'em' : ';font-style: oblique', @@ -83,9 +83,6 @@ element_styles['tt'] = element_styles['kbd'] element_styles['i'] = element_styles['em'] element_styles['b'] = element_styles['strong'] -class_styles = { -} - ''' ========== JEP-0071 @@ -182,40 +179,43 @@ for name in BLOCK_HEAD: def build_patterns(view, config, interface): - #extra, rst does not mark _underline_ or /it/ up - #actually , or are not in the JEP-0071, but are seen in the wild + # extra, rst does not mark _underline_ or /it/ up + # actually , or are not in the JEP-0071, but are seen in the wild basic_pattern = r'(? deadline: + gajim.log.debug(str('Timeout loading image %s ' % \ + attrs['src'] + ex)) + mem = '' + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Timeout loading image') + break + try: + temp = f.read(100) + except socket.timeout, ex: + gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \ + str(ex)) + mem = '' + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Timeout loading image') + break + if temp: + mem += temp + else: + break + if len(mem) > 2*1024*1024: + alt = attrs.get('alt', '') + if alt: + alt += '\n' + alt += _('Image is too big') + break + pixbuf = None + if mem: + # Caveat: GdkPixbuf is known not to be safe to load + # images from network... this program is now potentially + # hackable ;) + loader = gtk.gdk.PixbufLoader() + dims = [0,0] + def height_cb(length): + dims[1] = length + def width_cb(length): + dims[0] = length + # process width and height attributes + w = attrs.get('width') + h = attrs.get('height') + # override with width and height styles + for attr, val in style_iter(attrs.get('style', '')): + if attr == 'width': + w = val + elif attr == 'height': + h = val + if w: + self._parse_length(w, False, False, 1, 1000, width_cb) + if h: + self._parse_length(h, False, False, 1, 1000, height_cb) + def set_size(pixbuf, w, h, dims): + '''FIXME: floats should be relative to the whole + textview, and resize with it. This needs new + pifbufs for every resize, gtk.gdk.Pixbuf.scale_simple + or similar. + ''' + if type(dims[0]) == float: + dims[0] = int(dims[0]*w) + elif not dims[0]: + dims[0] = w + if type(dims[1]) == float: + dims[1] = int(dims[1]*h) + if not dims[1]: + dims[1] = h + loader.set_size(*dims) + if w or h: + loader.connect('size-prepared', set_size, dims) + loader.write(mem) + loader.close() + pixbuf = loader.get_pixbuf() + alt = attrs.get('alt', '') + if pixbuf is not None: + tags = self._get_style_tags() + if tags: + tmpmark = self.textbuf.create_mark(None, self.iter, True) + self.textbuf.insert_pixbuf(self.iter, pixbuf) + self.starting = False + if tags: + start = self.textbuf.get_iter_at_mark(tmpmark) + for tag in tags: + self.textbuf.apply_tag(tag, start, self.iter) + self.textbuf.delete_mark(tmpmark) + else: + self._insert_text('[IMG: %s]' % alt) + except Exception, ex: + gajim.log.error('Error loading image ' + str(ex)) + pixbuf = None + alt = attrs.get('alt', 'Broken image') + try: + loader.close() + except: + pass + return pixbuf def _begin_span(self, style, tag=None, id_=None): if style is None: @@ -511,9 +680,9 @@ class HtmlHandler(xml.sax.handler.ContentHandler): tag = self.textbuf.create_tag(id_) else: tag = self.textbuf.create_tag() # we create anonymous tag - for attr, val in [item.split(':', 1) for item in style.split(';') if len(item.strip())]: - attr = attr.strip().lower() - val = val.strip() + for attr, val in style_iter(style): + attr = attr.lower() + val = val try: method = self.__style_methods[attr] except KeyError: @@ -603,6 +772,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler): self.text += content return if allwhitespace_rx.match(content) is not None and self._starts_line(): + self.text += ' ' return self.text += content self.starting = False @@ -611,9 +781,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler): def startElement(self, name, attrs): self._flush_text() klass = [i for i in attrs.get('class',' ').split(' ') if i] - style = attrs.get('style','') + style = '' #Add styles defined for classes - #TODO: priority between class and style elements? for k in klass: if k in classes: style += classes[k] @@ -641,9 +810,13 @@ class HtmlHandler(xml.sax.handler.ContentHandler): tag.is_anchor = True elif name in LIST_ELEMS: style += ';margin-left: 2em' + elif name == 'img': + tag = self._process_img(attrs) if name in element_styles: style += element_styles[name] - + # so that explicit styles override implicit ones, + # we add the attribute last + style += ";"+attrs.get('style','') if style == '': style = None self._begin_span(style, tag, id_) @@ -681,83 +854,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler): elif name == 'dt': if not self.starting: self._jump_line() - elif name == 'img': - # Wait maximum 1s for connection - socket.setdefaulttimeout(1) - try: - f = urllib2.urlopen(attrs['src']) - except Exception, ex: - gajim.log.debug(str('Error loading image %s ' % attrs['src'] + ex)) - pixbuf = None - alt = attrs.get('alt', 'Broken image') - else: - # Wait 10ms between each byte - try: - f.fp._sock.fp._sock.settimeout(0.1) - except: - pass - # Max image size = 2 MB (to try to prevent DoS) in Max 3s - mem = '' - deadline = time.time() + 3 - while True: - if time.time() > deadline: - gajim.log.debug(str('Timeout loading image %s ' % \ - attrs['src'] + ex)) - mem = '' - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Timeout loading image') - break - try: - temp = f.read(100) - except socket.timeout, ex: - gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \ - str(ex)) - mem = '' - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Timeout loading image') - break - if temp: - mem += temp - else: - break - if len(mem) > 2*1024*1024: - alt = attrs.get('alt', '') - if alt: - alt += '\n' - alt += _('Image is too big') - break - - if mem: - # Caveat: GdkPixbuf is known not to be safe to load - # images from network... this program is now potentially - # hackable ;) - loader = gtk.gdk.PixbufLoader() - loader.write(mem) - loader.close() - pixbuf = loader.get_pixbuf() - else: - pixbuf = None - if pixbuf is not None: - tags = self._get_style_tags() - if tags: - tmpmark = self.textbuf.create_mark(None, self.iter, True) - - self.textbuf.insert_pixbuf(self.iter, pixbuf) - - if tags: - start = self.textbuf.get_iter_at_mark(tmpmark) - for tag in tags: - self.textbuf.apply_tag(tag, start, self.iter) - self.textbuf.delete_mark(tmpmark) - else: - self._insert_text('[IMG: %s]' % alt) - elif name == 'body' or name == 'html': - pass - elif name == 'a': + elif name in ('a', 'img', 'body', 'html'): pass elif name in INLINE: pass @@ -807,10 +904,6 @@ class HtmlHandler(xml.sax.handler.ContentHandler): # self.text = ' ' class HtmlTextView(gtk.TextView): - __gtype_name__ = 'HtmlTextView' - __gsignals__ = { - 'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type - } def __init__(self): gobject.GObject.__init__(self) @@ -886,16 +979,39 @@ class HtmlTextView(gtk.TextView): parser.setContentHandler(HtmlHandler(self, eob)) parser.parse(StringIO(html)) + # too much space after :) #if not eob.starts_line(): # buffer.insert(eob, '\n') + change_cursor = None if __name__ == '__main__': + import os + from common import gajim + + class log(object): + + def debug(self, text): + print "debug:", text + def warn(self, text): + print "warn;", text + def error(self,text): + print "error;", text + + gajim.log=log() + + if gajim.config.get('emoticons_theme'): + print "emoticons" htmlview = HtmlTextView() + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') + # use this for hr + htmlview.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + + tooltip = tooltips.BaseTooltip() def on_textview_motion_notify_event(widget, event): '''change the cursor to a hand when we are over a mail or an url''' @@ -965,9 +1081,9 @@ if __name__ == '__main__':
Hey, are you licensed to Jabber?
+ alt='A License to Jabber' + width='50%' height='50%' + /> ''') htmlview.display_html('