def process_response(self, request, response): """ Transform the response with Diazo if transformable """ content = response if check_themes_enabled(request): theme = get_active_theme(request) if theme: rules_file = os.path.join(theme.theme_path(), 'rules.xml') if theme.id != self.theme_id or not os.path.exists( rules_file) or theme.debug: if not theme.builtin: if theme.rules: fp = open(rules_file, 'w') try: fp.write(theme.rules.serialize()) finally: fp.close() self.theme_id = theme.id self.diazo = DiazoMiddleware( app=self.app, global_conf=None, rules=rules_file, prefix=theme.theme_url(), doctype=DOCTYPE, ) compiled_theme = self.diazo.compile_theme() self.transform = etree.XSLT( compiled_theme, access_control=self.diazo.access_control) self.params = {} for key, value in self.diazo.environ_param_map.items(): if key in request.environ: if value in self.diazo.unquoted_params: self.params[value] = request.environ[key] else: self.params[value] = quote_param( request.environ[key]) try: if isinstance(response, etree._Element): response = HttpResponse() else: parser = etree.HTMLParser(remove_blank_text=True, remove_comments=True) content = etree.fromstring(response.content, parser) result = self.transform(content.decode('utf-8'), **self.params) response.content = XMLSerializer( result, doctype=DOCTYPE).serialize() except Exception, e: getLogger('django_diazo').error(e)
def __call__(self, environ, start_response): request = Request(environ) response = request.get_response(self.app) app_iter = response(environ, start_response) if self.should_ignore(request) or not self.should_transform(response): return app_iter # Set up parameters params = {} for key, value in self.environ_param_map.items(): if key in environ: params[value] = quote_param(environ[key]) for key, value in self.params.items(): params[key] = quote_param(value) # Apply the transformation app_iter = getHTMLSerializer(app_iter) tree = self.transform(app_iter.tree, **params) # Set content type and choose XHTML or HTML serializer serializer = html.tostring response.headers['Content-Type'] = 'text/html' if tree.docinfo.doctype and 'XHTML' in tree.docinfo.doctype: serializer = etree.tostring response.headers['Content-Type'] = 'application/xhtml+xml' app_iter = XMLSerializer(tree, serializer=serializer) # Calculate the content length - we still return the parsed tree # so that other middleware could avoid having to re-parse, even if # we take a hit on serialising here if self.update_content_length and 'Content-Length' in response.headers: response.headers['Content-Length'] = str(len(str(app_iter))) # Return a repoze.xmliter XMLSerializer, which helps avoid re-parsing # the content tree in later middleware stages return app_iter
def build_xsl_params_document(xsl_params): if xsl_params is None: xsl_params = {} if not 'path' in xsl_params: xsl_params['path'] = '' known_params = etree.XML('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />') for param_name, param_value in xsl_params.items(): param_element = etree.SubElement(known_params, "{http://www.w3.org/1999/XSL/Transform}param") param_element.attrib['name'] = param_name if isinstance(param_value, basestring): param_element.text = param_value else: param_element.attrib['select'] = str(quote_param(param_value)) param_element.tail = '\n' return known_params
def process_response(self, request, response): """ Transform the response with Diazo if transformable """ content = response if check_themes_enabled(request): theme = get_active_theme(request) if theme: rules_file = os.path.join(theme.theme_path(), 'rules.xml') if theme.id != self.theme_id or not os.path.exists(rules_file) or theme.debug: if not theme.builtin: if theme.rules: fp = open(rules_file, 'w') try: fp.write(theme.rules.serialize()) finally: fp.close() self.theme_id = theme.id self.diazo = DiazoMiddleware( app=self.app, global_conf=None, rules=rules_file, prefix=theme.theme_url(), doctype=DOCTYPE, ) compiled_theme = self.diazo.compile_theme() self.transform = etree.XSLT(compiled_theme, access_control=self.diazo.access_control) self.params = {} for key, value in self.diazo.environ_param_map.items(): if key in request.environ: if value in self.diazo.unquoted_params: self.params[value] = request.environ[key] else: self.params[value] = quote_param(request.environ[key]) try: if isinstance(response, etree._Element): response = HttpResponse() else: parser = etree.HTMLParser(remove_blank_text=True, remove_comments=True) content = etree.fromstring(response.content, parser) result = self.transform(content, **self.params) response.content = XMLSerializer(result, doctype=DOCTYPE).serialize() except Exception, e: getLogger('django_diazo').error(e)
def build_xsl_params_document(xsl_params): if xsl_params is None: xsl_params = {} if not 'path' in xsl_params: xsl_params['path'] = '' known_params = etree.XML( '<xsl:stylesheet version="1.0" ' 'xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />') for param_name, param_value in xsl_params.items(): param_element = etree.SubElement( known_params, "{http://www.w3.org/1999/XSL/Transform}param") param_element.attrib['name'] = param_name if isinstance(param_value, basestring): param_element.text = param_value else: param_element.attrib['select'] = str(quote_param(param_value)) param_element.tail = '\n' return known_params
def main(): """Called from console script """ op = _createOptionParser(usage=usage) op.add_option("-x", "--xsl", metavar="transform.xsl", help="XSL transform", dest="xsl", default=None) op.add_option("--path", metavar="PATH", help="URI path", dest="path", default=None) op.add_option( "--parameters", metavar="param1=val1,param2=val2", help="Set the values of arbitrary parameters", dest="parameters", default=None, ) op.add_option( "--runtrace-xml", metavar="runtrace.xml", help="Write an xml format runtrace to file", dest="runtrace_xml", default=None, ) op.add_option( "--runtrace-html", metavar="runtrace.html", help="Write an html format runtrace to file", dest="runtrace_html", default=None, ) (options, args) = op.parse_args() if len(args) > 2: op.error("Wrong number of arguments.") elif len(args) == 2: if options.xsl or options.rules: op.error("Wrong number of arguments.") path, content = args if path.lower().endswith(".xsl"): options.xsl = path else: options.rules = path elif len(args) == 1: content, = args else: op.error("Wrong number of arguments.") if options.rules is None and options.xsl is None: op.error("Must supply either options or rules") if options.trace: logger.setLevel(logging.DEBUG) runtrace = False if options.runtrace_xml or options.runtrace_html: runtrace = True parser = etree.HTMLParser() parser.resolvers.add(RunResolver(os.path.dirname(content))) if options.xsl is not None: output_xslt = etree.parse(options.xsl) else: xsl_params = None if options.xsl_params: xsl_params = split_params(options.xsl_params) output_xslt = compile_theme( rules=options.rules, theme=options.theme, extra=options.extra, parser=parser, read_network=options.read_network, absolute_prefix=options.absolute_prefix, includemode=options.includemode, indent=options.pretty_print, xsl_params=xsl_params, runtrace=runtrace, ) if content == "-": content = sys.stdin if options.read_network: access_control = AC_READ_NET else: access_control = AC_READ_FILE transform = etree.XSLT(output_xslt, access_control=access_control) content_doc = etree.parse(content, parser=parser) params = {} if options.path is not None: params["path"] = "'%s'" % options.path if options.parameters: for key, value in split_params(options.parameters).items(): params[key] = quote_param(value) output_html = transform(content_doc, **params) if isinstance(options.output, basestring): out = open(options.output, "wt") else: out = options.output out.write(str(output_html)) if runtrace: runtrace_doc = diazo.runtrace.generate_runtrace(rules=options.rules, error_log=transform.error_log) if options.runtrace_xml: if options.runtrace_xml == "-": out = sys.stdout else: out = open(options.runtrace_xml, "wt") runtrace_doc.write(out, encoding="utf-8", pretty_print=options.pretty_print) if options.runtrace_html: if options.runtrace_html == "-": out = sys.stdout else: out = open(options.runtrace_html, "wt") out.write(str(diazo.runtrace.runtrace_to_html(runtrace_doc))) for msg in transform.error_log: if not msg.message.startswith("<runtrace "): logger.warn(msg)
def testAll(self): self.errors = StringIO() config = ConfigParser.ConfigParser() config.read([defaultsfn, os.path.join(self.testdir, "options.cfg")]) themefn = None if config.get('diazotest', 'theme'): themefn = os.path.join(self.testdir, config.get('diazotest', 'theme')) contentfn = os.path.join(self.testdir, "content.html") rulesfn = os.path.join(self.testdir, "rules.xml") xpathsfn = os.path.join(self.testdir, "xpaths.txt") xslfn = os.path.join(self.testdir, "compiled.xsl") outputfn = os.path.join(self.testdir, "output.html") xsl_params = {} extra_params = config.get('diazotest', 'extra-params') if extra_params: for token in extra_params.split(' '): token_split = token.split(':') xsl_params[token_split[0]] = len(token_split) > 1 and token_split[1] or None if not os.path.exists(rulesfn): return contentdoc = etree.parse(source=contentfn, base_url=contentfn, parser=etree.HTMLParser()) # Make a compiled version theme_parser = etree.HTMLParser() ct = diazo.compiler.compile_theme( rules=rulesfn, theme=themefn, parser=theme_parser, absolute_prefix=config.get('diazotest', 'absolute-prefix'), indent=config.getboolean('diazotest', 'pretty-print'), xsl_params=xsl_params, ) # Serialize / parse the theme - this can catch problems with escaping. cts = etree.tostring(ct) parser = etree.XMLParser() etree.fromstring(cts, parser=parser) # Compare to previous version if os.path.exists(xslfn): old = open(xslfn).read() new = cts if old != new: if self.writefiles: open(xslfn + '.old', 'w').write(old) if self.warnings: print "WARNING:", "compiled.xsl has CHANGED" for line in difflib.unified_diff(old.split('\n'), new.split('\n'), xslfn, 'now'): print line # Write the compiled xsl out to catch unexpected changes if self.writefiles: open(xslfn, 'w').write(cts) # Apply the compiled version, then test against desired output theme_parser.resolvers.add(diazo.run.RunResolver(self.testdir)) processor = etree.XSLT(ct) params = {} params['path'] = "'%s'" % config.get('diazotest', 'path') for key in xsl_params: try: params[key] = quote_param(config.get('diazotest', key)) except ConfigParser.NoOptionError: pass result = processor(contentdoc, **params) # Read the whole thing to strip off xhtml namespace. # If we had xslt 2.0 then we could use xpath-default-namespace. self.themed_string = str(result) self.themed_content = etree.ElementTree(file=StringIO(self.themed_string), parser=etree.HTMLParser()) # remove the extra meta content type metas = self.themed_content.xpath("/html/head/meta[@http-equiv='Content-Type']") if metas: meta = metas[0] meta.getparent().remove(meta) if os.path.exists(xpathsfn): for xpath in open(xpathsfn).readlines(): # Read the XPaths from the file, skipping blank lines and # comments this_xpath = xpath.strip() if not this_xpath or this_xpath[0] == '#': continue assert self.themed_content.xpath(this_xpath), "%s: %s" % (xpathsfn, this_xpath) # Compare to previous version if os.path.exists(outputfn): old = open(outputfn).read() new = self.themed_string if old != new: #if self.writefiles: # open(outputfn + '.old', 'w').write(old) for line in difflib.unified_diff(old.split('\n'), new.split('\n'), outputfn, 'now'): print line assert old == new, "output.html has CHANGED" # Write out the result to catch unexpected changes if self.writefiles: open(outputfn, 'w').write(self.themed_string)
def transformIterable(self, result, encoding): """Apply the transform if required """ result = self.parseTree(result) if result is None: return None transform = self.setupTransform() if transform is None: return None # Find real or virtual path - PATH_INFO has VHM elements in it url = self.request.get('ACTUAL_URL', '') # Find the host name base = self.request.get('BASE1', '') path = url[len(base):] parts = urlsplit(base.lower()) params = dict( url=quote_param(url), base=quote_param(base), path=quote_param(path), scheme=quote_param(parts.scheme), host=quote_param(parts.netloc), ) # Add expression-based parameters toadapt = (getSite(), self.request) settings = getMultiAdapter(toadapt, IThemeSettings) if settings.doctype: result.doctype = settings.doctype if not result.doctype.endswith('\n'): result.doctype += '\n' parameterExpressions = settings.parameterExpressions or {} if parameterExpressions: cache = getCache(settings) DevelopmentMode = Globals.DevelopmentMode # Compile and cache expressions expressions = None if not DevelopmentMode: expressions = cache.expressions if expressions is None: expressions = {} for name, expressionText in parameterExpressions.items(): expressions[name] = compileExpression(expressionText) if not DevelopmentMode: cache.updateExpressions(expressions) # Execute all expressions context = findContext(self.published) expressionContext = createExpressionContext(context, self.request) for name, expression in expressions.items(): params[name] = quote_param(expression(expressionContext)) transformed = transform(result.tree, **params) if transformed is None: return None result.tree = transformed return result
def main(): """Called from console script """ op = _createOptionParser(usage=usage) op.add_option("-x", "--xsl", metavar="transform.xsl", help="XSL transform", dest="xsl", default=None) op.add_option("--path", metavar="PATH", help="URI path", dest="path", default=None) op.add_option("--parameters", metavar="param1=val1,param2=val2", help="Set the values of arbitrary parameters", dest="parameters", default=None) (options, args) = op.parse_args() if len(args) > 2: op.error("Wrong number of arguments.") elif len(args) == 2: if options.xsl or options.rules: op.error("Wrong number of arguments.") path, content = args if path.lower().endswith('.xsl'): options.xsl = path else: options.rules = path elif len(args) == 1: content, = args else: op.error("Wrong number of arguments.") if options.rules is None and options.xsl is None: op.error("Must supply either options or rules") if options.trace: logger.setLevel(logging.DEBUG) parser = etree.HTMLParser() parser.resolvers.add(RunResolver(os.path.dirname(content))) if options.xsl is not None: output_xslt = etree.parse(options.xsl) else: xsl_params = None if options.xsl_params: xsl_params = split_params(options.xsl_params) output_xslt = compile_theme( rules=options.rules, theme=options.theme, extra=options.extra, parser=parser, read_network=options.read_network, absolute_prefix=options.absolute_prefix, includemode=options.includemode, indent=options.pretty_print, xsl_params=xsl_params, ) if content == '-': content = sys.stdin if options.read_network: access_control = AC_READ_NET else: access_control = AC_READ_FILE transform = etree.XSLT(output_xslt, access_control=access_control) content_doc = etree.parse(content, parser=parser) params = {} if options.path is not None: params['path'] = "'%s'" % options.path if options.parameters: for key, value in split_params(options.parameters).items(): params[key] = quote_param(value) output_html = transform(content_doc, **params) if isinstance(options.output, basestring): out = open(options.output, 'wt') else: out = options.output out.write(str(output_html)) for msg in transform.error_log: logger.warn(msg)
def compile_theme(rules, theme=None, extra=None, css=True, xinclude=True, absolute_prefix=None, update=True, trace=False, includemode=None, parser=None, compiler_parser=None, rules_parser=None, access_control=None, read_network=False, indent=None, xsl_params=None, runtrace=False): """Invoke the diazo compiler. * ``rules`` is the rules file * ``theme`` is the theme file * ``extra`` is an optional XSLT file with Diazo extensions (depracated, use inline xsl in the rules instead) * ``css`` can be set to False to disable CSS syntax support (providing a moderate speed gain) * ``xinclude`` can be set to False to disable XInclude support during the compile phase (providing a moderate speed gain) * ``absolute_prefix`` can be set to a string that will be prefixed to any *relative* URL referenced in an image, link or stylesheet in the theme HTML file before the theme is passed to the compiler. This allows a theme to be written so that it can be opened and views standalone on the filesystem, even if at runtime its static resources are going to be served from some other location. For example, an ``<img src="images/foo.jpg" />`` can be turned into ``<img src="/static/images/foo.jpg" />`` with an ``absolute_prefix`` of "/static". * ``update`` can be set to False to disable the automatic update support for the old Deliverance 0.2 namespace (for a moderate speed gain) * ``trace`` can be set to True to enable compiler trace information * ``runtrace`` can be set to True to add tracing into the XSL output * ``includemode`` can be set to 'document', 'esi' or 'ssi' to change the way in which includes are processed * ``parser`` can be set to an lxml parser instance; the default is an HTMLParser * ``compiler_parser``` can be set to an lxml parser instance; the default is a XMLParser * ``rules_parser`` can be set to an lxml parser instance; the default is a XMLParser. * ``xsl_params`` can be set to a dictionary of parameters that will be known to the compiled theme transform. The keys should be the parameter names. Values are default values. """ if access_control is not None: read_network = access_control.options['read_network'] rules_doc = process_rules( rules=rules, theme=theme, extra=extra, css=css, xinclude=xinclude, absolute_prefix=absolute_prefix, update=update, trace=trace, includemode=includemode, parser=parser, rules_parser=rules_parser, read_network=read_network, ) # Build a document with all the <xsl:param /> values to set the defaults # for every value passed in as xsl_params known_params = build_xsl_params_document(xsl_params) # Create a pseudo resolver for this known_params_url = 'file:///__diazo_known_params__' emit_stylesheet_resolver = CustomResolver({ known_params_url: etree.tostring(known_params)}) emit_stylesheet_parser = etree.XMLParser() emit_stylesheet_parser.resolvers.add(emit_stylesheet_resolver) # Set up parameters params = {} if indent is not None: params['indent'] = indent and "'yes'" or "'no'" params['known_params_url'] = quote_param(known_params_url) params['runtrace'] = '1' if runtrace else '0' # Run the final stage compiler emit_stylesheet = pkg_xsl( 'emit-stylesheet.xsl', parser=emit_stylesheet_parser) compiled_doc = emit_stylesheet(rules_doc, **params) compiled_doc = set_parser(etree.tostring(compiled_doc), parser, compiler_parser) return compiled_doc
def getFrame(self): """AJAX method to load a frame's contents Expects two query string parameters: ``path`` - the path to fetch - and ``theme``, which can be 'off', to disable the theme and 'apply' to apply the current theme to the response. Additionally: - a query string parameter ``links`` can be set to one of ``disable`` or ``replace``. The former will disable hyperlinks; the latter will replace them with links using the ``@@themeing-controlpanel-getframe`` view. - a query string parameter ``forms`` can be set to one of ``disable`` or ``replace``. The former will disable forms ; the latter will replace them with links using the ``@@themeing-controlpanel-getframe`` view. - a query string parameter ``title`` can be set to give a new page title """ processInputs(self.request) path = self.request.form.get('path', None) theme = self.request.form.get('theme', 'off') links = self.request.form.get('links', None) forms = self.request.form.get('forms', None) title = self.request.form.get('title', None) if not path: return "<html><head></head><body></body></html>" portal = getPortal() portal_url = portal.absolute_url() response = subrequest(path, root=portal) result = response.getBody() content_type = response.headers.get('content-type') encoding = None if content_type is not None and ';' in content_type: content_type, encoding = content_type.split(';', 1) if encoding is None: encoding = 'utf-8' else: # e.g. charset=utf-8 encoding = encoding.split('=', 1)[1].strip() # Not HTML? Return as-is if content_type is None or not content_type.startswith('text/html'): if len(result) == 0: result = ' ' # Zope does not deal well with empty responses return result result = result.decode(encoding).encode('ascii', 'xmlcharrefreplace') if len(result) == 0: result = ' ' # Zope does not deal well with empty responses if theme == 'off': self.request.response.setHeader('X-Theme-Disabled', '1') elif theme == 'apply': self.request.response.setHeader('X-Theme-Disabled', '1') themeInfo = getThemeFromResourceDirectory(self.context) registry = getUtility(IRegistry) settings = registry.forInterface(IThemeSettings, False) context = self.context try: context = findContext(portal.restrictedTraverse(path)) except (KeyError, NotFound,): pass serializer = getHTMLSerializer([result], pretty_print=False) try: transform = compileThemeTransform(themeInfo.rules, themeInfo.absolutePrefix, settings.readNetwork, themeInfo.parameterExpressions or {}) except lxml.etree.XMLSyntaxError, e: return self.theme_error_template(error=e.msg) params = prepareThemeParameters(context, self.request, themeInfo.parameterExpressions or {}) # Fix url and path since the request gave us this view params['url'] = quote_param("%s%s" % (portal_url, path,)) params['path'] = quote_param("%s%s" % (portal.absolute_url_path(), path,)) if themeInfo.doctype: serializer.doctype = themeInfo.doctype if not serializer.doctype.endswith('\n'): serializer.doctype += '\n' serializer.tree = transform(serializer.tree, **params) result = ''.join(serializer)
def main(): """Called from console script """ op = _createOptionParser(usage=usage) op.add_option( '-x', '--xsl', metavar='transform.xsl', help='XSL transform', dest='xsl', default=None, ) op.add_option( '--path', metavar='PATH', help='URI path', dest='path', default=None, ) op.add_option( '--parameters', metavar='param1=val1,param2=val2', help='Set the values of arbitrary parameters', dest='parameters', default=None, ) op.add_option( '--runtrace-xml', metavar='runtrace.xml', help='Write an xml format runtrace to file', dest='runtrace_xml', default=None, ) op.add_option( '--runtrace-html', metavar='runtrace.html', help='Write an html format runtrace to file', dest='runtrace_html', default=None, ) (options, args) = op.parse_args() if len(args) > 2: op.error('Wrong number of arguments.') elif len(args) == 2: if options.xsl or options.rules: op.error('Wrong number of arguments.') path, content = args if path.lower().endswith('.xsl'): options.xsl = path else: options.rules = path elif len(args) == 1: content, = args else: op.error('Wrong number of arguments.') if options.rules is None and options.xsl is None: op.error('Must supply either options or rules') if options.trace: logger.setLevel(logging.DEBUG) runtrace = False if options.runtrace_xml or options.runtrace_html: runtrace = True parser = etree.HTMLParser() parser.resolvers.add(RunResolver(os.path.dirname(content))) if options.xsl is not None: output_xslt = etree.parse(options.xsl) else: xsl_params = None if options.xsl_params: xsl_params = split_params(options.xsl_params) output_xslt = compile_theme( rules=options.rules, theme=options.theme, extra=options.extra, parser=parser, read_network=options.read_network, absolute_prefix=options.absolute_prefix, includemode=options.includemode, indent=options.pretty_print, xsl_params=xsl_params, runtrace=runtrace, ) if content == '-': content = sys.stdin if options.read_network: access_control = AC_READ_NET else: access_control = AC_READ_FILE transform = etree.XSLT(output_xslt, access_control=access_control) content_doc = etree.parse(content, parser=parser) params = {} if options.path is not None: params['path'] = "'{path}'".format(path=options.path) if options.parameters: for key, value in split_params(options.parameters).items(): params[key] = quote_param(value) output_html = transform(content_doc, **params) if isinstance(options.output, string_types): out = open(options.output, 'wt') else: out = options.output out.write(str(output_html)) if runtrace: runtrace_doc = diazo.runtrace.generate_runtrace( rules=options.rules, error_log=transform.error_log, ) if options.runtrace_xml: if options.runtrace_xml == '-': out = sys.stdout else: out = open(options.runtrace_xml, 'wt') runtrace_doc.write( out, encoding='utf-8', pretty_print=options.pretty_print, ) if options.runtrace_html: if options.runtrace_html == '-': out = sys.stdout else: out = open(options.runtrace_html, 'wt') out.write(str(diazo.runtrace.runtrace_to_html(runtrace_doc))) for msg in transform.error_log: if not msg.message.startswith('<runtrace '): logger.warn(msg)
def getFrame(self): """AJAX method to load a frame's contents Expects two query string parameters: ``path`` - the path to fetch - and ``theme``, which can be 'off', to disable the theme and 'apply' to apply the current theme to the response. Additionally: - a query string parameter ``links`` can be set to one of ``disable`` or ``replace``. The former will disable hyperlinks; the latter will replace them with links using the ``@@themeing-controlpanel-getframe`` view. - a query string parameter ``forms`` can be set to one of ``disable`` or ``replace``. The former will disable forms ; the latter will replace them with links using the ``@@themeing-controlpanel-getframe`` view. - a query string parameter ``title`` can be set to give a new page title """ processInputs(self.request) path = self.request.form.get('path', None) theme = self.request.form.get('theme', 'off') links = self.request.form.get('links', None) forms = self.request.form.get('forms', None) title = self.request.form.get('title', None) if not path: return "<html><head></head><body></body></html>" portal = getPortal() portal_url = portal.absolute_url() response = subrequest(path, root=portal) result = response.getBody() content_type = response.headers.get('content-type') encoding = None if content_type is not None and ';' in content_type: content_type, encoding = content_type.split(';', 1) if encoding is None: encoding = 'utf-8' else: # e.g. charset=utf-8 encoding = encoding.split('=', 1)[1].strip() # Not HTML? Return as-is if content_type is None or not content_type.startswith('text/html'): if len(result) == 0: result = ' ' # Zope does not deal well with empty responses return result result = result.decode(encoding).encode('ascii', 'xmlcharrefreplace') if len(result) == 0: result = ' ' # Zope does not deal well with empty responses if theme == 'off': self.request.response.setHeader('X-Theme-Disabled', '1') elif theme == 'apply': self.request.response.setHeader('X-Theme-Disabled', '1') themeInfo = getThemeFromResourceDirectory(self.context) policy = theming_policy(self.request) settings = policy.getSettings() context = self.context try: context = findContext(portal.restrictedTraverse(path)) except (KeyError, NotFound,): pass serializer = getHTMLSerializer([result], pretty_print=False) try: transform = compileThemeTransform( themeInfo.rules, themeInfo.absolutePrefix, settings.readNetwork, themeInfo.parameterExpressions or {}) except lxml.etree.XMLSyntaxError as e: return self.theme_error_template(error=e.msg) params = prepareThemeParameters( context, self.request, themeInfo.parameterExpressions or {}) # Fix url and path since the request gave us this view params['url'] = quote_param(''.join((portal_url, path,))) params['path'] = quote_param( ''.join((portal.absolute_url_path(), path,)) ) if themeInfo.doctype: serializer.doctype = themeInfo.doctype if not serializer.doctype.endswith('\n'): serializer.doctype += '\n' serializer.tree = transform(serializer.tree, **params) result = ''.join(serializer) if title or links or forms: tree = lxml.html.fromstring(result) def encodeUrl(orig): origUrl = urllib.parse.urlparse(orig) newPath = origUrl.path newQuery = urllib.parse.parse_qs(origUrl.query) # relative? if not origUrl.netloc: newPath = urllib.parse.urljoin( path.rstrip("/") + "/", newPath.lstrip("/")) elif not orig.lower().startswith(portal_url.lower()): # Not an internal URL - ignore return orig newQuery['path'] = newPath newQuery['theme'] = theme if links: newQuery['links'] = links if forms: newQuery['forms'] = forms if title: if isinstance(title, six.text_type): newQuery['title'] = title.encode('utf-8', 'replace') else: newQuery['title'] = title return self.request.getURL() + '?' + urllib.parse.urlencode(newQuery) if title: titleElement = tree.cssselect("html head title") if titleElement: titleElement[0].text = title else: headElement = tree.cssselect("html head") if headElement: headElement[0].append(lxml.html.builder.TITLE(title)) if links: for n in tree.cssselect("a[href]"): if links == 'disable': n.attrib['href'] = '#' elif links == 'replace': n.attrib['href'] = encodeUrl(n.attrib['href']) if forms: for n in tree.cssselect("form[action]"): if forms == 'disable': n.attrib['action'] = '#' n.attrib['onsubmit'] = 'javascript:return false;' elif forms == 'replace': n.attrib['action'] = encodeUrl(n.attrib['action']) result = lxml.html.tostring(tree) return result
def __call__(self, environ, start_response): request = Request(environ) if self.should_ignore(request): return self.app(environ, start_response) if self.remove_conditional_headers: request.remove_conditional_headers() else: # Always remove Range and Accept-Encoding headers request.remove_conditional_headers( remove_encoding=True, remove_range=False, remove_match=False, remove_modified=True, ) response = request.get_response(self.app) if not self.should_transform(response): return response(environ, start_response) try: input_encoding = response.charset # Note, the Content-Length header will not be set if request.method == 'HEAD': self.reset_headers(response) return response(environ, start_response) # Prepare the serializer try: serializer = getHTMLSerializer( response.app_iter, encoding=input_encoding, ) except etree.XMLSyntaxError: # Abort transform on syntax error for empty response # Headers should be left intact return response(environ, start_response) finally: if getattr(response.app_iter, 'close', None): response.app_iter.close() self.reset_headers(response) # Set up parameters params = {} for key, value in self.environ_param_map.items(): if key in environ: if value in self.unquoted_params: params[value] = environ[key] else: params[value] = quote_param(environ[key]) for key, value in self.params.items(): if key in self.unquoted_params: params[key] = value else: params[key] = quote_param(value) # Apply the transformation tree = self.transform(serializer.tree, **params) # Set content type (normally inferred from stylesheet) # Unfortunately lxml does not expose docinfo.mediaType if self.content_type is None: if tree.getroot().tag == 'html': response.content_type = 'text/html' else: response.content_type = 'text/xml' response.charset = tree.docinfo.encoding or self.charset # Return a repoze.xmliter XMLSerializer, which helps avoid re-parsing # the content tree in later middleware stages. response.app_iter = XMLSerializer(tree, doctype=self.doctype) # Calculate the content length - we still return the parsed tree # so that other middleware could avoid having to re-parse, even if # we take a hit on serialising here if self.update_content_length: response.content_length = len(bytes(response.app_iter)) return response(environ, start_response)
def main(): """Called from console script """ op = _createOptionParser(usage=usage) op.add_option("-x", "--xsl", metavar="transform.xsl", help="XSL transform", dest="xsl", default=None) op.add_option("--path", metavar="PATH", help="URI path", dest="path", default=None) op.add_option("--parameters", metavar="param1=val1,param2=val2", help="Set the values of arbitrary parameters", dest="parameters", default=None) (options, args) = op.parse_args() if len(args) > 2: op.error("Wrong number of arguments.") elif len(args) == 2: if options.xsl or options.rules: op.error("Wrong number of arguments.") path, content = args if path.lower().endswith('.xsl'): options.xsl = path else: options.rules = path elif len(args) == 1: content, = args else: op.error("Wrong number of arguments.") if options.rules is None and options.xsl is None: op.error("Must supply either options or rules") if options.trace: logger.setLevel(logging.DEBUG) parser = etree.HTMLParser() parser.resolvers.add(RunResolver(os.path.dirname(content))) if options.xsl is not None: output_xslt = etree.parse(options.xsl) else: xsl_params=None if options.xsl_params: xsl_params = split_params(options.xsl_params) output_xslt = compile_theme( rules=options.rules, theme=options.theme, extra=options.extra, parser=parser, read_network=options.read_network, absolute_prefix=options.absolute_prefix, includemode=options.includemode, indent=options.pretty_print, xsl_params=xsl_params, ) if content == '-': content = sys.stdin if options.read_network: access_control = AC_READ_NET else: access_control = AC_READ_FILE transform = etree.XSLT(output_xslt, access_control=access_control) content_doc = etree.parse(content, parser=parser) params = {} if options.path is not None: params['path'] = "'%s'" % options.path if options.parameters: for key, value in split_params(options.parameters).items(): params[key] = quote_param(value) output_html = transform(content_doc, **params) if isinstance(options.output, basestring): out = open(options.output, 'wt') else: out = options.output out.write(str(output_html)) for msg in transform.error_log: logger.warn(msg)
def __call__(self, environ, start_response): request = Request(environ) ignore = self.should_ignore(request) if not ignore: # We do not deal with Range requests try: del request.headers['Range'] except KeyError: pass response = request.get_response(self.app) sr = self._sr(start_response) app_iter = response(environ, sr) if ignore or not self.should_transform(response): start_response(self._status, self._response_headers, self._exc_info) return app_iter # Set up parameters params = {} for key, value in self.environ_param_map.items(): if key in environ: if value in self.unquoted_params: params[value] = environ[key] else: params[value] = quote_param(environ[key]) for key, value in self.params.items(): if key in self.unquoted_params: params[key] = value else: params[key] = quote_param(value) # Apply the transformation app_iter = getHTMLSerializer(app_iter) tree = self.transform(app_iter.tree, **params) # Set content type # Unfortunately lxml does not expose docinfo.mediaType content_type = self.content_type if content_type is None: if tree.getroot().tag == 'html': content_type = 'text/html' else: content_type = 'text/xml' encoding = tree.docinfo.encoding if not encoding: encoding = "UTF-8" response.headers['Content-Type'] = '%s; charset=%s' % (content_type, encoding) app_iter = XMLSerializer(tree, doctype=self.doctype) # Calculate the content length - we still return the parsed tree # so that other middleware could avoid having to re-parse, even if # we take a hit on serialising here if self.update_content_length and 'Content-Length' in response.headers: response.headers['Content-Length'] = str(len(str(app_iter))) # Remove Content-Range if set by the application we theme if self.update_content_length and 'Content-Range' in response.headers: del(response.headers['Content-Range']) # Start response here, after we update response headers self._response_headers = response.headers.items() start_response(self._status, self._response_headers, self._exc_info) # Return a repoze.xmliter XMLSerializer, which helps avoid re-parsing # the content tree in later middleware stages return app_iter
def testAll(self): self.errors = BytesIO() config = configparser.ConfigParser() config.read([defaultsfn, os.path.join(self.testdir, "options.cfg")]) themefn = None if config.get('diazotest', 'theme'): themefn = os.path.join(self.testdir, config.get('diazotest', 'theme')) contentfn = os.path.join(self.testdir, "content.html") rulesfn = os.path.join(self.testdir, "rules.xml") xpathsfn = os.path.join(self.testdir, "xpaths.txt") xslfn = os.path.join(self.testdir, "compiled.xsl") outputfn = os.path.join(self.testdir, "output.html") xsl_params = {} extra_params = config.get('diazotest', 'extra-params') if extra_params: for token in extra_params.split(' '): token_split = token.split(':') xsl_params[token_split[0]] = len(token_split) > 1 and \ token_split[1] or None if not os.path.exists(rulesfn): return contentdoc = etree.parse(source=contentfn, base_url=contentfn, parser=etree.HTMLParser()) # Make a compiled version theme_parser = etree.HTMLParser() ct = diazo.compiler.compile_theme( rules=rulesfn, theme=themefn, parser=theme_parser, absolute_prefix=config.get('diazotest', 'absolute-prefix'), indent=config.getboolean('diazotest', 'pretty-print'), xsl_params=xsl_params, ) # Serialize / parse the theme - this can catch problems with escaping. cts = etree.tostring(ct, encoding='unicode') parser = etree.XMLParser() etree.fromstring(cts, parser=parser) # Compare to previous version if os.path.exists(xslfn): with open(xslfn) as f: old = f.read() new = cts if old != new: if self.writefiles: with open(xslfn + '.old', 'w') as f: f.write(old) if self.warnings: print("WARNING:", "compiled.xsl has CHANGED") for line in difflib.unified_diff(old.split(u'\n'), new.split(u'\n'), xslfn, 'now'): print(line) # Write the compiled xsl out to catch unexpected changes if self.writefiles: with open(xslfn, 'w') as f: f.write(cts) # Apply the compiled version, then test against desired output theme_parser.resolvers.add(diazo.run.RunResolver(self.testdir)) processor = etree.XSLT(ct) params = {} params['path'] = "'%s'" % config.get('diazotest', 'path') for key in xsl_params: try: params[key] = quote_param(config.get('diazotest', key)) except configparser.NoOptionError: pass result = processor(contentdoc, **params) # Read the whole thing to strip off xhtml namespace. # If we had xslt 2.0 then we could use xpath-default-namespace. self.themed_string = str(result) self.themed_content = etree.ElementTree(file=StringIO( self.themed_string), parser=etree.HTMLParser()) # remove the extra meta content type metas = self.themed_content.xpath( "/html/head/meta[@http-equiv='Content-Type']") if metas: meta = metas[0] meta.getparent().remove(meta) if os.path.exists(xpathsfn): with open(xpathsfn) as f: for xpath in f.readlines(): # Read the XPaths from the file, skipping blank lines and # comments this_xpath = xpath.strip() if not this_xpath or this_xpath[0] == '#': continue assert self.themed_content.xpath( this_xpath), "%s: %s" % (xpathsfn, this_xpath) # Compare to previous version if os.path.exists(outputfn): with open(outputfn) as f: old = f.read() new = self.themed_string if not xml_compare(etree.fromstring(old.strip()), etree.fromstring(new.strip())): # if self.writefiles: # open(outputfn + '.old', 'w').write(old) for line in difflib.unified_diff(old.split(u'\n'), new.split(u'\n'), outputfn, 'now'): print(line) assert old == new, "output.html has CHANGED" # Write out the result to catch unexpected changes if self.writefiles: with open(outputfn, 'w') as f: f.write(self.themed_string)
def compile_theme(rules, theme=None, extra=None, css=True, xinclude=True, absolute_prefix=None, update=True, trace=False, includemode=None, parser=None, compiler_parser=None, rules_parser=None, access_control=None, read_network=False, indent=None, xsl_params=None, runtrace=False): """Invoke the diazo compiler. * ``rules`` is the rules file * ``theme`` is the theme file * ``extra`` is an optional XSLT file with Diazo extensions (depracated, use inline xsl in the rules instead) * ``css`` can be set to False to disable CSS syntax support (providing a moderate speed gain) * ``xinclude`` can be set to False to disable XInclude support during the compile phase (providing a moderate speed gain) * ``absolute_prefix`` can be set to a string that will be prefixed to any *relative* URL referenced in an image, link or stylesheet in the theme HTML file before the theme is passed to the compiler. This allows a theme to be written so that it can be opened and views standalone on the filesystem, even if at runtime its static resources are going to be served from some other location. For example, an ``<img src="images/foo.jpg" />`` can be turned into ``<img src="/static/images/foo.jpg" />`` with an ``absolute_prefix`` of "/static". * ``update`` can be set to False to disable the automatic update support for the old Deliverance 0.2 namespace (for a moderate speed gain) * ``trace`` can be set to True to enable compiler trace information * ``runtrace`` can be set to True to add tracing into the XSL output * ``includemode`` can be set to 'document', 'esi' or 'ssi' to change the way in which includes are processed * ``parser`` can be set to an lxml parser instance; the default is an HTMLParser * ``compiler_parser``` can be set to an lxml parser instance; the default is a XMLParser * ``rules_parser`` can be set to an lxml parser instance; the default is a XMLParser. * ``xsl_params`` can be set to a dictionary of parameters that will be known to the compiled theme transform. The keys should be the parameter names. Values are default values. """ if access_control is not None: read_network = access_control.options['read_network'] rules_doc = process_rules( rules=rules, theme=theme, extra=extra, css=css, xinclude=xinclude, absolute_prefix=absolute_prefix, update=update, trace=trace, includemode=includemode, parser=parser, rules_parser=rules_parser, read_network=read_network, ) # Build a document with all the <xsl:param /> values to set the defaults # for every value passed in as xsl_params known_params = build_xsl_params_document(xsl_params) # Create a pseudo resolver for this known_params_url = 'file:///__diazo_known_params__' emit_stylesheet_resolver = CustomResolver( {known_params_url: etree.tostring(known_params)}) emit_stylesheet_parser = etree.XMLParser() emit_stylesheet_parser.resolvers.add(emit_stylesheet_resolver) # Set up parameters params = {} if indent is not None: params['indent'] = indent and "'yes'" or "'no'" params['known_params_url'] = quote_param(known_params_url) params['runtrace'] = '1' if runtrace else '0' # Run the final stage compiler emit_stylesheet = pkg_xsl('emit-stylesheet.xsl', parser=emit_stylesheet_parser) compiled_doc = emit_stylesheet(rules_doc, **params) compiled_doc = set_parser(etree.tostring(compiled_doc), parser, compiler_parser) return compiled_doc
def __call__(self, environ, start_response): request = Request(environ) if self.should_ignore(request): return self.app(environ, start_response) if self.remove_conditional_headers: request.remove_conditional_headers() else: # Always remove Range and Accept-Encoding headers request.remove_conditional_headers( remove_encoding=True, remove_range=False, remove_match=False, remove_modified=True, ) response = request.get_response(self.app) if not self.should_transform(response): return response(environ, start_response) try: input_encoding = response.charset # Note, the Content-Length header will not be set if request.method == 'HEAD': self.reset_headers(response) return response(environ, start_response) # Prepare the serializer try: serializer = getHTMLSerializer(response.app_iter, encoding=input_encoding) except etree.XMLSyntaxError: # Abort transform on syntax error for empty response # Headers should be left intact return response(environ, start_response) finally: if hasattr(response.app_iter, 'close'): response.app_iter.close() self.reset_headers(response) # Set up parameters params = {} for key, value in self.environ_param_map.items(): if key in environ: if value in self.unquoted_params: params[value] = environ[key] else: params[value] = quote_param(environ[key]) for key, value in self.params.items(): if key in self.unquoted_params: params[key] = value else: params[key] = quote_param(value) # Apply the transformation tree = self.transform(serializer.tree, **params) # Set content type (normally inferred from stylesheet) # Unfortunately lxml does not expose docinfo.mediaType if self.content_type is None: if tree.getroot().tag == 'html': response.content_type = 'text/html' else: response.content_type = 'text/xml' response.charset = tree.docinfo.encoding or self.charset # Return a repoze.xmliter XMLSerializer, which helps avoid re-parsing # the content tree in later middleware stages. response.app_iter = XMLSerializer(tree, doctype=self.doctype) # Calculate the content length - we still return the parsed tree # so that other middleware could avoid having to re-parse, even if # we take a hit on serialising here if self.update_content_length: response.content_length = len(str(response.app_iter)) return response(environ, start_response)