From 96e6b34cd7a96e73961e8ad5d394210d4b0fa84c Mon Sep 17 00:00:00 2001
From: Yann Leboulanger
Date: Tue, 29 May 2007 20:22:41 +0000
Subject: [PATCH] [sgala and I] better support of sizes in XHTML, simplify rst
generator
---
src/common/rst_xhtml_generator.py | 92 ++++---
src/htmltextview.py | 401 +++++++++++++++++++-----------
2 files changed, 321 insertions(+), 172 deletions(-)
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('
')
@@ -992,8 +1108,9 @@ if __name__ == '__main__':
One
Two is nested:
- One
- - Two
- - Three
+ - Two
+ - Three
+ - Four
Three