from ZPublisher.BeforeTraverse import registerBeforeTraverse from ZPublisher.BeforeTraverse import unregisterBeforeTraverse from ZPublisher.BeforeTraverse import queryBeforeTraverse from OFS.SimpleItem import SimpleItem from OFS.PropertyManager import PropertyManager from deliverance.interpreter import Renderer from deliverance import htmlserialize from lxml import etree import urllib2 import re class DeliveranceRule(SimpleItem, PropertyManager): """ An object which invokes a deliverance rule when its container is traversed """ meta_type = 'Deliverance Rule' theme_uri = '' rule = '' transform_ports = () id = 'deliverance_rule' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'theme_uri', 'type':'string', 'mode':'w', 'label':'Theme URI'}, {'id':'rule', 'type':'text', 'mode':'w', 'label':'Rule'}, {'id':'transform_ports', 'type':'lines', 'mode':'w', 'label':'Transform Ports'}, ) manage_options = PropertyManager.manage_options def manage_afterAdd(self, item, container): existing = queryBeforeTraverse(container, 'Deliverance') if existing: raise ValueError('A Deliverance Rule named %r is already in ' 'this container' % existing[0][0]) registerBeforeTraverse(container, self, 'Deliverance', 1) def manage_beforeDelete(self, item, container): unregisterBeforeTraverse(container, 'Deliverance') def __call__(self, container, request): response = request.RESPONSE port = request.SERVER_PORT if self.transform_ports and port not in self.transform_ports: return if not self.rule: # Hasn't been initialized return orig_setBody = response.setBody def setBody(*arg, **kw): orig_setBody(*arg, **kw) if not response.headers.get('content-type', '').startswith('text/html'): return response body = self.transform_body(response) return orig_setBody(body) response.setBody = setBody def transform_body(self, response): interp = self.make_renderer() body = response.body content_type = response.headers['content-type'] match = self._meta_charset_re.search(body) if match: body = body.decode(match.group(1), 'ignore') else: match = self._charset_re.search(content_type) if match: body = body.decode(match.group(1), 'ignore') content_type = content_type.split(';')[0] + '; charset=UTF-8' response.headers['content-type'] = content_type transformed = interp.render(etree.HTML(body)) return htmlserialize.tostring(transformed) def make_renderer(self): return Renderer( theme=self.get_theme(), theme_uri=self.theme_uri, rule=self.get_rule(), rule_uri=self.get_rule_uri(), reference_resolver=self.resolve_reference) def get_theme(self): return self.resolve_reference(self.theme_uri, 'html') def get_rule(self): return etree.XML(self.rule) def get_rule_uri(self): self.absolute_url() + '/rule' def resolve_reference(self, href, parse, encoding=None): text = self.get_resource(href) if parse == "xml": return etree.XML(text) if parse == "html": return etree.HTML(text) else: if encoding: return text.decode(encoding) else: return text _charset_re = re.compile(r'charset=([a-z0-9_-]+)', re.I) _meta_charset_re = re.compile(r']*charset=([a-z0-9_-]+).*?>', re.I) def get_resource(self, href): if href.startswith('data:'): return href[5:] # @@: This is a really bad implementation req = urllib2.Request( href, headers={ 'Accept': 'text/html,application/xhtml+xml'}, unverifiable=True) f = urllib2.urlopen(req) c = f.read() match = self._meta_charset_re.search(c) if match: c = c.decode(match.group(1), 'ignore') else: content_type = f.info().get('Content-Type') f.close() if content_type: match = self._charset_re.search(content_type) if match: c = c.decode(match.group(1), 'ignore') return c def manage_addDeliveranceRule(self, REQUEST=None): """ Add a deliverance rule """ rule = DeliveranceRule() self._setObject(rule.getId(), rule) rule_url = rule.absolute_url() + '/manage_main' if REQUEST is not None: REQUEST.RESPONSE.redirect(rule_url)