def parse_xml(cls, el, source_location): """ Creates a Rule object from a parsed XML <rule> element. """ assert el.tag == 'rule' classes = el.get('class', '').split() if not classes: classes = ['default'] theme = None actions = [] suppress_standard = asbool(el.get('suppress-standard')) for child in el.iterchildren(): if child.tag == 'theme': ## FIXME: error if more than one theme theme = Theme.parse_xml(child, source_location) continue if child.tag is etree.Comment: continue action = parse_action(child, source_location) actions.append(action) match = None inst = cls(classes, actions, theme, match, suppress_standard, source_location) for attr in RuleMatch.match_attrs: if el.get(attr): inst.match = RuleMatch.parse_xml(inst, el, source_location) ## FIXME: would last="1" be a good alternative to suppress-standard? break return inst
def parse_xml(cls, doc, source_location): """ Parses the given XML/etree document into an instance of this class. """ assert doc.tag == 'ruleset' matchers = [] clientsides = [] rules = [] default_theme = None for el in doc.iterchildren(): if el.tag == 'match': matcher = Match.parse_xml(el, source_location) matchers.append(matcher) elif el.tag == 'clientside': matcher = ClientsideMatch.parse_xml(el, source_location) clientsides.append(matcher) elif el.tag == 'rule': rule = Rule.parse_xml(el, source_location) rules.append(rule) elif el.tag == 'theme': ## FIXME: Add parse error default_theme = Theme.parse_xml(el, source_location) elif el.tag in ('proxy', 'server-settings', Comment): # Handled elsewhere, so we just ignore this element continue else: ## FIXME: source location? raise DeliveranceSyntaxError( "Invalid tag %s (unknown tag name %r)" % (tostring(el).split('>', 1)[0] + '>', el.tag), element=el) rules_by_class = {} for rule in rules: for class_name in rule.classes: rules_by_class.setdefault(class_name, []).append(rule) return cls(matchers, clientsides, rules_by_class, default_theme=default_theme, source_location=source_location)
def parse_xml(cls, doc, source_location): """ Parses the given XML/etree document into an instance of this class. """ assert doc.tag == 'ruleset' matchers = [] clientsides = [] rules = [] default_theme = None for el in doc.iterchildren(): if el.tag == 'match': matcher = Match.parse_xml(el, source_location) matchers.append(matcher) elif el.tag == 'clientside': matcher = ClientsideMatch.parse_xml(el, source_location) clientsides.append(matcher) elif el.tag == 'rule': rule = Rule.parse_xml(el, source_location) rules.append(rule) elif el.tag == 'theme': ## FIXME: Add parse error default_theme = Theme.parse_xml(el, source_location) elif el.tag in ('proxy', 'server-settings', Comment): # Handled elsewhere, so we just ignore this element continue else: ## FIXME: source location? raise DeliveranceSyntaxError( "Invalid tag %s (unknown tag name %r)" % (tostring(el).split('>', 1)[0]+'>', el.tag), element=el) rules_by_class = {} for rule in rules: for class_name in rule.classes: rules_by_class.setdefault(class_name, []).append(rule) return cls(matchers, clientsides, rules_by_class, default_theme=default_theme, source_location=source_location)
def apply_rules(self, req, resp, resource_fetcher, log, default_theme=None): """ Apply the whatever the appropriate rules are to the request/response. """ extra_headers = parse_meta_headers(resp.body) if extra_headers: response_headers = HeaderDict(resp.headerlist + extra_headers) else: response_headers = resp.headers try: classes = run_matches(self.matchers, req, resp, response_headers, log) except AbortTheme: return resp if 'X-Deliverance-Page-Class' in response_headers: log.debug(self, "Found page class %s in headers", response_headers['X-Deliverance-Page-Class'].strip()) classes.extend(response_headers['X-Deliverance-Page-Class'].strip().split()) if 'deliverance.page_classes' in req.environ: log.debug(self, "Found page class in WSGI environ: %s", ' '.join(req.environ["deliverance.page_classes"])) classes.extend(req.environ['deliverance.page_classes']) if not classes: classes = ['default'] rules = [] theme = None for class_name in classes: ## FIXME: handle case of unknown classes ## Or do that during compilation? for rule in self.rules_by_class.get(class_name, []): if rule not in rules: rules.append(rule) if rule.theme: theme = rule.theme if theme is None: theme = self.default_theme if theme is None and default_theme is not None: theme = Theme(href=default_theme, source_location=self.source_location) if theme is None: log.error(self, "No theme has been defined for the request") return resp try: theme_href = theme.resolve_href(req, resp, log) original_theme_resp = self.get_theme_response( theme_href, resource_fetcher, log) theme_doc = self.get_theme_doc( original_theme_resp, theme_href, should_escape_cdata=True, should_fix_meta_charset_position=True) resp = force_charset(resp) body = resp.unicode_body body = escape_cdata(body) body = fix_meta_charset_position(body) content_doc = self.parse_document(body, req.url) run_standard = True for rule in rules: if rule.match is not None: matches = rule.match(req, resp, response_headers, log) if not matches: log.debug(rule, "Skipping <rule>") continue rule.apply(content_doc, theme_doc, resource_fetcher, log) if rule.suppress_standard: run_standard = False if run_standard: ## FIXME: should it be possible to put the standard rule in the ruleset? standard_rule.apply(content_doc, theme_doc, resource_fetcher, log) except AbortTheme: return resp remove_content_attribs(theme_doc) ## FIXME: handle caching? if original_theme_resp.body.strip().startswith("<!DOCTYPE"): tree = theme_doc.getroottree() else: tree = content_doc.getroottree() if "XHTML" in tree.docinfo.doctype: method = "xml" else: method = "html" theme_str = tostring(theme_doc, include_meta_content_type=True) theme_str = tree.docinfo.doctype + theme_str theme_doc = document_fromstring(theme_str) tree = theme_doc.getroottree() resp.body = tostring(tree, method=method, include_meta_content_type=True) resp.body = unescape_cdata(resp.body) return resp
def apply_rules(self, req, resp, resource_fetcher, log, default_theme=None): """ Apply the whatever the appropriate rules are to the request/response. """ extra_headers = parse_meta_headers(resp.body) if extra_headers: response_headers = ResponseHeaders(resp.headerlist + extra_headers) else: response_headers = resp.headers try: classes = run_matches(self.matchers, req, resp, response_headers, log) except AbortTheme: return resp if 'X-Deliverance-Page-Class' in response_headers: log.debug(self, "Found page class %s in headers", response_headers['X-Deliverance-Page-Class'].strip()) classes.extend(response_headers['X-Deliverance-Page-Class'].strip().split()) if 'deliverance.page_classes' in req.environ: log.debug(self, "Found page class in WSGI environ: %s", ' '.join(req.environ["deliverance.page_classes"])) classes.extend(req.environ['deliverance.page_classes']) if not classes: classes = ['default'] rules = [] theme = None for class_name in classes: ## FIXME: handle case of unknown classes ## Or do that during compilation? for rule in self.rules_by_class.get(class_name, []): if rule not in rules: rules.append(rule) if rule.theme: theme = rule.theme if theme is None: theme = self.default_theme if theme is None and default_theme is not None: theme = Theme(href=default_theme, source_location=self.source_location) if theme is None: log.error(self, "No theme has been defined for the request") return resp try: theme_href = theme.resolve_href(req, resp, log) original_theme_resp = self.get_theme_response( theme_href, resource_fetcher, log) theme_doc = self.get_theme_doc( original_theme_resp, theme_href, should_escape_cdata=True, should_fix_meta_charset_position=True) resp = force_charset(resp) body = resp.unicode_body body = escape_cdata(body) body = fix_meta_charset_position(body) content_doc = self.parse_document(body, req.url) run_standard = True for rule in rules: if rule.match is not None: matches = rule.match(req, resp, response_headers, log) if not matches: log.debug(rule, "Skipping <rule>") continue rule.apply(content_doc, theme_doc, resource_fetcher, log) if rule.suppress_standard: run_standard = False if run_standard: ## FIXME: should it be possible to put the standard rule in the ruleset? standard_rule.apply(content_doc, theme_doc, resource_fetcher, log) except AbortTheme: return resp remove_content_attribs(theme_doc) ## FIXME: handle caching? if original_theme_resp.body.strip().startswith("<!DOCTYPE"): tree = theme_doc.getroottree() else: tree = content_doc.getroottree() if "XHTML" in tree.docinfo.doctype: method = "xml" else: method = "html" theme_str = tostring(theme_doc, include_meta_content_type=True) theme_str = tree.docinfo.doctype + theme_str theme_doc = document_fromstring(theme_str) tree = theme_doc.getroottree() resp.body = tostring(tree, method=method, include_meta_content_type=True) resp.body = unescape_cdata(resp.body) return resp