[sgala and I] better support of sizes in XHTML, simplify rst generator
This commit is contained in:
parent
fc59489bdf
commit
96e6b34cd7
2 changed files with 321 additions and 172 deletions
|
@ -21,39 +21,70 @@ try:
|
||||||
from docutils import nodes,utils
|
from docutils import nodes,utils
|
||||||
from docutils.parsers.rst.roles import set_classes
|
from docutils.parsers.rst.roles import set_classes
|
||||||
except:
|
except:
|
||||||
|
print "Requires docutils 0.4 for set_classes to be available"
|
||||||
def create_xhtml(text):
|
def create_xhtml(text):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
def jep_reference_role(role, rawtext, text, lineno, inliner,
|
def pos_int_validator(text):
|
||||||
options={}, content=[]):
|
"""Validates that text can be evaluated as a positive integer."""
|
||||||
'''Role to make handy references to Jabber Enhancement Proposals (JEP).
|
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).
|
def generate_uri_role( role_name, aliases,
|
||||||
Modeled after the sample in docutils documentation.
|
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,
|
||||||
jep_base_url = 'http://www.jabber.org/jeps/'
|
options={}, content=[]):
|
||||||
jep_url = 'jep-%04d.html'
|
|
||||||
try:
|
try:
|
||||||
jepnum = int(text)
|
valid_text = validator(text)
|
||||||
if jepnum <= 0:
|
except ValueError, e:
|
||||||
raise ValueError
|
msg = inliner.reporter.error( e.message % dict(text=text), line=lineno)
|
||||||
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)
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
return [prb], [msg]
|
return [prb], [msg]
|
||||||
ref = jep_base_url + jep_url % jepnum
|
ref = base_url + interpret_url % valid_text
|
||||||
set_classes(options)
|
set_classes(options)
|
||||||
node = nodes.reference(rawtext, 'JEP ' + utils.unescape(text), refuri=ref,
|
node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref,
|
||||||
**options)
|
**options)
|
||||||
return [node], []
|
return [node], []
|
||||||
|
|
||||||
roles.register_canonical_role('jep-reference', jep_reference_role)
|
uri_reference_role.__doc__ = """Role to make handy references to URIs.
|
||||||
from docutils.parsers.rst.languages.en import roles
|
|
||||||
roles['jep-reference'] = 'jep-reference'
|
Use as :%(role_name)s:`71` (or any of %(aliases)s).
|
||||||
roles['jep'] = 'jep-reference'
|
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:
|
class HTMLGenerator:
|
||||||
'''Really simple HTMLGenerator starting from publish_parts.
|
'''Really simple HTMLGenerator starting from publish_parts.
|
||||||
|
@ -108,7 +139,7 @@ else:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print Generator.create_xhtml('''
|
print "test 1\n", Generator.create_xhtml("""
|
||||||
test::
|
test::
|
||||||
|
|
||||||
>>> print 1
|
>>> print 1
|
||||||
|
@ -118,9 +149,10 @@ test::
|
||||||
|
|
||||||
this `` should trigger`` should trigger the problem.
|
this `` should trigger`` should trigger the problem.
|
||||||
|
|
||||||
''')
|
""")
|
||||||
print Generator.create_xhtml('''
|
print "test 2\n", Generator.create_xhtml("""
|
||||||
*test1
|
*test1
|
||||||
|
|
||||||
test2_
|
test2_
|
||||||
''')
|
""")
|
||||||
|
print "test 3\n", Generator.create_xhtml(""":ticket:`316` implements :xep:`71`""")
|
||||||
|
|
|
@ -40,9 +40,9 @@ import time
|
||||||
import urllib2
|
import urllib2
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from common import i18n
|
||||||
from common import gajim
|
from common import gajim
|
||||||
#from common import i18n
|
|
||||||
|
|
||||||
|
|
||||||
import tooltips
|
import tooltips
|
||||||
|
|
||||||
|
@ -56,13 +56,13 @@ allwhitespace_rx = re.compile('^\\s*$')
|
||||||
display_resolution = 0.3514598*(gtk.gdk.screen_height() /
|
display_resolution = 0.3514598*(gtk.gdk.screen_height() /
|
||||||
float(gtk.gdk.screen_height_mm()))
|
float(gtk.gdk.screen_height_mm()))
|
||||||
|
|
||||||
#embryo of CSS classes
|
# embryo of CSS classes
|
||||||
classes = {
|
classes = {
|
||||||
#'system-message':';display: none',
|
#'system-message':';display: none',
|
||||||
'problematic':';color: red',
|
'problematic':';color: red',
|
||||||
}
|
}
|
||||||
|
|
||||||
#styles for elemens
|
# styles for elements
|
||||||
element_styles = {
|
element_styles = {
|
||||||
'u' : ';text-decoration: underline',
|
'u' : ';text-decoration: underline',
|
||||||
'em' : ';font-style: oblique',
|
'em' : ';font-style: oblique',
|
||||||
|
@ -83,9 +83,6 @@ element_styles['tt'] = element_styles['kbd']
|
||||||
element_styles['i'] = element_styles['em']
|
element_styles['i'] = element_styles['em']
|
||||||
element_styles['b'] = element_styles['strong']
|
element_styles['b'] = element_styles['strong']
|
||||||
|
|
||||||
class_styles = {
|
|
||||||
}
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
==========
|
==========
|
||||||
JEP-0071
|
JEP-0071
|
||||||
|
@ -182,13 +179,14 @@ for name in BLOCK_HEAD:
|
||||||
|
|
||||||
|
|
||||||
def build_patterns(view, config, interface):
|
def build_patterns(view, config, interface):
|
||||||
#extra, rst does not mark _underline_ or /it/ up
|
# extra, rst does not mark _underline_ or /it/ up
|
||||||
#actually <b>, <i> or <u> are not in the JEP-0071, but are seen in the wild
|
# actually <b>, <i> or <u> are not in the JEP-0071, but are seen in the wild
|
||||||
basic_pattern = r'(?<!\w|\<|/|:)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\w|/|:)|'\
|
basic_pattern = r'(?<!\w|\<|/|:)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\w|/|:)|'\
|
||||||
r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
|
r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
|
||||||
view.basic_pattern_re = re.compile(basic_pattern)
|
view.basic_pattern_re = re.compile(basic_pattern)
|
||||||
#TODO: emoticons
|
# emoticons
|
||||||
emoticons_pattern = ''
|
emoticons_pattern = ''
|
||||||
|
try:
|
||||||
if config.get('emoticons_theme'):
|
if config.get('emoticons_theme'):
|
||||||
# When an emoticon is bordered by an alpha-numeric character it is NOT
|
# When an emoticon is bordered by an alpha-numeric character it is NOT
|
||||||
# expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
|
# expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
|
||||||
|
@ -216,6 +214,8 @@ def build_patterns(view, config, interface):
|
||||||
'(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \
|
'(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \
|
||||||
'(?:' + emoticons_pattern[:-1] + ')' + \
|
'(?:' + emoticons_pattern[:-1] + ')' + \
|
||||||
'(?:(?![\w.]' + emoticons_pattern_postmatch[:-1] + '))'
|
'(?:(?![\w.]' + emoticons_pattern_postmatch[:-1] + '))'
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# because emoticons match later (in the string) they need to be after
|
# because emoticons match later (in the string) they need to be after
|
||||||
# basic matches that may occur earlier
|
# basic matches that may occur earlier
|
||||||
|
@ -231,9 +231,16 @@ def _parse_css_color(color):
|
||||||
else:
|
else:
|
||||||
return gtk.gdk.color_parse(color)
|
return gtk.gdk.color_parse(color)
|
||||||
|
|
||||||
|
def style_iter(style):
|
||||||
|
return (map(lambda x:x.strip(),item.split(':', 1)) for item in style.split(';') if len(item.strip()))
|
||||||
|
|
||||||
|
|
||||||
class HtmlHandler(xml.sax.handler.ContentHandler):
|
class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
|
"""A handler to display html to a gtk textview.
|
||||||
|
|
||||||
|
It keeps a stack of "style spans" (start/end element pairs)
|
||||||
|
and a stack of list counters, for nested lists.
|
||||||
|
"""
|
||||||
def __init__(self, textview, startiter):
|
def __init__(self, textview, startiter):
|
||||||
xml.sax.handler.ContentHandler.__init__(self)
|
xml.sax.handler.ContentHandler.__init__(self)
|
||||||
self.textbuf = textview.get_buffer()
|
self.textbuf = textview.get_buffer()
|
||||||
|
@ -303,16 +310,20 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
frac, callback, args):
|
frac, callback, args):
|
||||||
callback(allocation.width*frac, *args)
|
callback(allocation.width*frac, *args)
|
||||||
|
|
||||||
def _parse_length(self, value, font_relative, callback, *args):
|
def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args):
|
||||||
'''Parse/calc length, converting to pixels, calls callback(length, *args)
|
'''Parse/calc length, converting to pixels, calls callback(length, *args)
|
||||||
when the length is first computed or changes'''
|
when the length is first computed or changes'''
|
||||||
if value.endswith('%'):
|
if value.endswith('%'):
|
||||||
frac = float(value[:-1])/100
|
val = float(value[:-1])
|
||||||
|
sign = cmp(val,0)
|
||||||
|
# limits: 1% to 500%
|
||||||
|
val = sign*max(1,min(abs(val),500))
|
||||||
|
frac = val/100
|
||||||
if font_relative:
|
if font_relative:
|
||||||
attrs = self._get_current_attributes()
|
attrs = self._get_current_attributes()
|
||||||
font_size = attrs.font.get_size() / pango.SCALE
|
font_size = attrs.font.get_size() / pango.SCALE
|
||||||
callback(frac*display_resolution*font_size, *args)
|
callback(frac*display_resolution*font_size, *args)
|
||||||
else:
|
elif block_relative:
|
||||||
# CSS says 'Percentage values: refer to width of the closest
|
# CSS says 'Percentage values: refer to width of the closest
|
||||||
# block-level ancestor'
|
# block-level ancestor'
|
||||||
# This is difficult/impossible to implement, so we use
|
# This is difficult/impossible to implement, so we use
|
||||||
|
@ -323,26 +334,41 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
self.textview.connect('size-allocate',
|
self.textview.connect('size-allocate',
|
||||||
self.__parse_length_frac_size_allocate,
|
self.__parse_length_frac_size_allocate,
|
||||||
frac, callback, args)
|
frac, callback, args)
|
||||||
|
else:
|
||||||
|
callback(frac, *args)
|
||||||
|
return
|
||||||
|
|
||||||
elif value.endswith('pt'): # points
|
val = float(value[:-2])
|
||||||
callback(float(value[:-2])*display_resolution, *args)
|
sign = cmp(val,0)
|
||||||
|
# validate length
|
||||||
|
val = sign*max(minl,min(abs(val*display_resolution),maxl))
|
||||||
|
if value.endswith('pt'): # points
|
||||||
|
callback(val*display_resolution, *args)
|
||||||
|
|
||||||
elif value.endswith('em'): # ems, the height of the element's font
|
elif value.endswith('em'): # ems, the width of the element's font
|
||||||
attrs = self._get_current_attributes()
|
attrs = self._get_current_attributes()
|
||||||
font_size = attrs.font.get_size() / pango.SCALE
|
font_size = attrs.font.get_size() / pango.SCALE
|
||||||
callback(float(value[:-2])*display_resolution*font_size, *args)
|
callback(val*display_resolution*font_size, *args)
|
||||||
|
|
||||||
elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
|
elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
|
||||||
# FIXME: figure out how to calculate this correctly
|
# FIXME: figure out how to calculate this correctly
|
||||||
# for now 'em' size is used as approximation
|
# for now 'em' size is used as approximation
|
||||||
attrs = self._get_current_attributes()
|
attrs = self._get_current_attributes()
|
||||||
font_size = attrs.font.get_size() / pango.SCALE
|
font_size = attrs.font.get_size() / pango.SCALE
|
||||||
callback(float(value[:-2])*display_resolution*font_size, *args)
|
callback(val*display_resolution*font_size, *args)
|
||||||
|
|
||||||
elif value.endswith('px'): # pixels
|
elif value.endswith('px'): # pixels
|
||||||
callback(int(value[:-2]), *args)
|
callback(val, *args)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
|
# TODO: isn't "no units" interpreted as pixels?
|
||||||
|
val = int(value)
|
||||||
|
sign = cmp(val,0)
|
||||||
|
# validate length
|
||||||
|
val = sign*max(minl,min(abs(val),maxl))
|
||||||
|
callback(val, *args)
|
||||||
|
except:
|
||||||
warnings.warn('Unable to parse length value "%s"' % value)
|
warnings.warn('Unable to parse length value "%s"' % value)
|
||||||
|
|
||||||
def __parse_font_size_cb(length, tag):
|
def __parse_font_size_cb(length, tag):
|
||||||
|
@ -352,7 +378,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
def _parse_style_display(self, tag, value):
|
def _parse_style_display(self, tag, value):
|
||||||
if value == 'none':
|
if value == 'none':
|
||||||
tag.set_property('invisible','true')
|
tag.set_property('invisible','true')
|
||||||
#Fixme: display: block, inline
|
# FIXME: display: block, inline
|
||||||
|
|
||||||
def _parse_style_font_size(self, tag, value):
|
def _parse_style_font_size(self, tag, value):
|
||||||
try:
|
try:
|
||||||
|
@ -377,7 +403,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
if value == 'larger':
|
if value == 'larger':
|
||||||
tag.set_property('scale', pango.SCALE_LARGE)
|
tag.set_property('scale', pango.SCALE_LARGE)
|
||||||
return
|
return
|
||||||
self._parse_length(value, True, self.__parse_font_size_cb, tag)
|
# font relative (5 ~ 4pt, 110 ~ 72pt)
|
||||||
|
self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag)
|
||||||
|
|
||||||
def _parse_style_font_style(self, tag, value):
|
def _parse_style_font_style(self, tag, value):
|
||||||
try:
|
try:
|
||||||
|
@ -399,11 +426,13 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
#__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
|
#__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
|
||||||
|
|
||||||
def _parse_style_margin_left(self, tag, value):
|
def _parse_style_margin_left(self, tag, value):
|
||||||
self._parse_length(value, False, self.__frac_length_tag_cb,
|
# block relative
|
||||||
|
self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
|
||||||
tag, 'left-margin')
|
tag, 'left-margin')
|
||||||
|
|
||||||
def _parse_style_margin_right(self, tag, value):
|
def _parse_style_margin_right(self, tag, value):
|
||||||
self._parse_length(value, False, self.__frac_length_tag_cb,
|
# block relative
|
||||||
|
self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
|
||||||
tag, 'right-margin')
|
tag, 'right-margin')
|
||||||
|
|
||||||
def _parse_style_font_weight(self, tag, value):
|
def _parse_style_font_weight(self, tag, value):
|
||||||
|
@ -470,12 +499,31 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
elif value == 'nowrap':
|
elif value == 'nowrap':
|
||||||
tag.set_property('wrap_mode', gtk.WRAP_NONE)
|
tag.set_property('wrap_mode', gtk.WRAP_NONE)
|
||||||
|
|
||||||
|
def __length_tag_cb(self, value, tag, propname):
|
||||||
|
try:
|
||||||
|
tag.set_property(propname, value)
|
||||||
|
except:
|
||||||
|
gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_style_width(self, tag, value):
|
||||||
|
if value == 'auto':
|
||||||
|
return
|
||||||
|
self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
|
||||||
|
tag, "width")
|
||||||
|
def _parse_style_height(self, tag, value):
|
||||||
|
if value == 'auto':
|
||||||
|
return
|
||||||
|
self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
|
||||||
|
tag, "height")
|
||||||
|
|
||||||
|
|
||||||
# build a dictionary mapping styles to methods, for greater speed
|
# build a dictionary mapping styles to methods, for greater speed
|
||||||
__style_methods = dict()
|
__style_methods = dict()
|
||||||
for style in ['background-color', 'color', 'font-family', 'font-size',
|
for style in ['background-color', 'color', 'font-family', 'font-size',
|
||||||
'font-style', 'font-weight', 'margin-left', 'margin-right',
|
'font-style', 'font-weight', 'margin-left', 'margin-right',
|
||||||
'text-align', 'text-decoration', 'white-space', 'display' ]:
|
'text-align', 'text-decoration', 'white-space', 'display',
|
||||||
|
'width', 'height' ]:
|
||||||
try:
|
try:
|
||||||
method = locals()['_parse_style_%s' % style.replace('-', '_')]
|
method = locals()['_parse_style_%s' % style.replace('-', '_')]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -489,6 +537,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
return [tag for tag in self.styles if tag is not None]
|
return [tag for tag in self.styles if tag is not None]
|
||||||
|
|
||||||
def _create_url(self, href, title, type_, id_):
|
def _create_url(self, href, title, type_, id_):
|
||||||
|
'''Process a url tag.
|
||||||
|
'''
|
||||||
tag = self.textbuf.create_tag(id_)
|
tag = self.textbuf.create_tag(id_)
|
||||||
if href and href[0] != '#':
|
if href and href[0] != '#':
|
||||||
tag.href = href
|
tag.href = href
|
||||||
|
@ -501,6 +551,125 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
tag.title = title
|
tag.title = title
|
||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
def _process_img(self, attrs):
|
||||||
|
'''Process a img tag.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
# 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 0.1s between each byte
|
||||||
|
try:
|
||||||
|
f.fp._sock.fp._sock.settimeout(0.5)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# Max image size = 2 MB (to try to prevent DoS)
|
||||||
|
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
|
||||||
|
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):
|
def _begin_span(self, style, tag=None, id_=None):
|
||||||
if style is None:
|
if style is None:
|
||||||
|
@ -511,9 +680,9 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
tag = self.textbuf.create_tag(id_)
|
tag = self.textbuf.create_tag(id_)
|
||||||
else:
|
else:
|
||||||
tag = self.textbuf.create_tag() # we create anonymous tag
|
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())]:
|
for attr, val in style_iter(style):
|
||||||
attr = attr.strip().lower()
|
attr = attr.lower()
|
||||||
val = val.strip()
|
val = val
|
||||||
try:
|
try:
|
||||||
method = self.__style_methods[attr]
|
method = self.__style_methods[attr]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -603,6 +772,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
self.text += content
|
self.text += content
|
||||||
return
|
return
|
||||||
if allwhitespace_rx.match(content) is not None and self._starts_line():
|
if allwhitespace_rx.match(content) is not None and self._starts_line():
|
||||||
|
self.text += ' '
|
||||||
return
|
return
|
||||||
self.text += content
|
self.text += content
|
||||||
self.starting = False
|
self.starting = False
|
||||||
|
@ -611,9 +781,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
def startElement(self, name, attrs):
|
def startElement(self, name, attrs):
|
||||||
self._flush_text()
|
self._flush_text()
|
||||||
klass = [i for i in attrs.get('class',' ').split(' ') if i]
|
klass = [i for i in attrs.get('class',' ').split(' ') if i]
|
||||||
style = attrs.get('style','')
|
style = ''
|
||||||
#Add styles defined for classes
|
#Add styles defined for classes
|
||||||
#TODO: priority between class and style elements?
|
|
||||||
for k in klass:
|
for k in klass:
|
||||||
if k in classes:
|
if k in classes:
|
||||||
style += classes[k]
|
style += classes[k]
|
||||||
|
@ -641,9 +810,13 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
tag.is_anchor = True
|
tag.is_anchor = True
|
||||||
elif name in LIST_ELEMS:
|
elif name in LIST_ELEMS:
|
||||||
style += ';margin-left: 2em'
|
style += ';margin-left: 2em'
|
||||||
|
elif name == 'img':
|
||||||
|
tag = self._process_img(attrs)
|
||||||
if name in element_styles:
|
if name in element_styles:
|
||||||
style += element_styles[name]
|
style += element_styles[name]
|
||||||
|
# so that explicit styles override implicit ones,
|
||||||
|
# we add the attribute last
|
||||||
|
style += ";"+attrs.get('style','')
|
||||||
if style == '':
|
if style == '':
|
||||||
style = None
|
style = None
|
||||||
self._begin_span(style, tag, id_)
|
self._begin_span(style, tag, id_)
|
||||||
|
@ -681,83 +854,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
elif name == 'dt':
|
elif name == 'dt':
|
||||||
if not self.starting:
|
if not self.starting:
|
||||||
self._jump_line()
|
self._jump_line()
|
||||||
elif name == 'img':
|
elif name in ('a', 'img', 'body', 'html'):
|
||||||
# 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':
|
|
||||||
pass
|
pass
|
||||||
elif name in INLINE:
|
elif name in INLINE:
|
||||||
pass
|
pass
|
||||||
|
@ -807,10 +904,6 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||||
# self.text = ' '
|
# self.text = ' '
|
||||||
|
|
||||||
class HtmlTextView(gtk.TextView):
|
class HtmlTextView(gtk.TextView):
|
||||||
__gtype_name__ = 'HtmlTextView'
|
|
||||||
__gsignals__ = {
|
|
||||||
'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
gobject.GObject.__init__(self)
|
gobject.GObject.__init__(self)
|
||||||
|
@ -886,16 +979,39 @@ class HtmlTextView(gtk.TextView):
|
||||||
parser.setContentHandler(HtmlHandler(self, eob))
|
parser.setContentHandler(HtmlHandler(self, eob))
|
||||||
parser.parse(StringIO(html))
|
parser.parse(StringIO(html))
|
||||||
|
|
||||||
|
# too much space after :)
|
||||||
#if not eob.starts_line():
|
#if not eob.starts_line():
|
||||||
# buffer.insert(eob, '\n')
|
# buffer.insert(eob, '\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
change_cursor = None
|
change_cursor = None
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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()
|
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()
|
tooltip = tooltips.BaseTooltip()
|
||||||
def on_textview_motion_notify_event(widget, event):
|
def on_textview_motion_notify_event(widget, event):
|
||||||
'''change the cursor to a hand when we are over a mail or an url'''
|
'''change the cursor to a hand when we are over a mail or an url'''
|
||||||
|
@ -966,8 +1082,8 @@ if __name__ == '__main__':
|
||||||
<p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
|
<p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
|
||||||
<p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
|
<p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
|
||||||
alt='A License to Jabber'
|
alt='A License to Jabber'
|
||||||
height='261'
|
width='50%' height='50%'
|
||||||
width='537'/></p>
|
/></p>
|
||||||
</body>
|
</body>
|
||||||
''')
|
''')
|
||||||
htmlview.display_html('<hr />')
|
htmlview.display_html('<hr />')
|
||||||
|
@ -992,8 +1108,9 @@ if __name__ == '__main__':
|
||||||
<li> One </li>
|
<li> One </li>
|
||||||
<li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
|
<li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
|
||||||
<li> One </li>
|
<li> One </li>
|
||||||
<li> Two </li>
|
<li style='font-size:50%'> Two </li>
|
||||||
<li> Three </li>
|
<li style='font-size:200%'> Three </li>
|
||||||
|
<li style='font-size:9999pt'> Four </li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
<li> Three </li></ol>
|
<li> Three </li></ol>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Add table
Reference in a new issue