# HG changeset patch # User aflett # Date 1205559749 0 # Node ID 1240ada13334fafa65fb659fdf95e3ccea247c97 # Parent 3d7288f373bd9959c05b6a262177c21325906d0d more code/comment clean up - make sure to retain match order diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -298,7 +298,8 @@ template = _apply_directives(template, ctxt, directives) remaining = match_set if 'match_once' not in hints: - # match has not been removed, so we need an exclusion matchset + # match has not been removed, so we need an + # exclusion matchset remaining = match_set.with_exclusion(match_template) body = self._exec(self._eval(self._flatten(template, ctxt), diff --git a/genshi/template/match.py b/genshi/template/match.py --- a/genshi/template/match.py +++ b/genshi/template/match.py @@ -18,23 +18,62 @@ class MatchSet(object): + """ A MatchSet is a set of matches discovered by the parser. This + class encapsulates the matching of a particular event to a set of + matches. It is optimized for basic tag matches, since that is by + far the most common use of py:match. + The two primary entry points into MatchSet are ``add``, which adds + a new py:match, and ``find_matches``, which returns all + /candidate/ match templates. The consumer of ``find_matches`` + still must call each candidates' match() to ensure the event + really matches, and to maintain state within the match. + + If a given py:match's path is simply a node name match, + (LocalNameTest) like "xyz", then MatchSet indexes that in a + dictionary that maps tag names to matches. + + If the path is more complex like "xyz[k=z]" then then that match + will always be returned by ``find_matches``. """ def __init__(self, parent=None, exclude=None): """ If a parent is given, it means this is a wrapper around another - set. Just copy references to member variables in parent, but - also set exclude + set. + + If exclude is given, it means include everything in the + parent, but exclude a specific match template. """ self.parent = parent + + self.current_index = 0 + if parent is None: + # merely for indexing. Note that this is shared between + # all MatchSets that share the same root parent. We don't have to worry about exclusions here + self.match_order = [] + + # tag_templates are match templates whose path are simply + # a tag, like "body" or "img" self.tag_templates = {} + + # other_templates include all other match templates, such + # as ones with complex paths like "[class=container]" self.other_templates = [] + + # exclude is a list of templates to ignore when iterating + # through templates self.exclude = [] if exclude is not None: self.exclude.append(exclude) else: + # We have a parent: Just copy references to member + # variables in parent so that there's no performance loss, + # but make our own exclusion set, so we don't have to + # chain exclusions across a chain of MatchSets + self.match_order = parent.match_order self.tag_templates = parent.tag_templates self.other_templates = parent.other_templates + self.exclude = copy(parent.exclude) if exclude is not None: self.exclude.append(exclude) @@ -44,8 +83,12 @@ match_template is a tuple the form test, path, template, hints, namespace, directives """ + + self.match_order.append(match_template) + path = match_template[1] - + + self.current_index += 1 if is_simple_path(path): # special cache of tag tag_name = path.paths[0][0][1].name @@ -93,9 +136,10 @@ new_match_set = cls(parent=self, exclude=exclude) return new_match_set - def find_matches(self, event): - """ - Return a list of all valid templates that can be used for the given event. + def find_raw_matches(self, event): + """ Return a list of all valid templates that can be used for the + given event. Ordering is funky because we first check + self.tag_templates, then check self.other_templates. """ kind, data, pos = event[:3] @@ -104,13 +148,24 @@ tag, attrs = data if tag.localname in self.tag_templates: for template in self.tag_templates[tag.localname]: - if template not in self.exclude: - yield template + yield template for template in self.other_templates: - if template not in self.exclude: - yield template + yield template + def find_matches(self, event): + """ Return a list of all valid templates that can be used for the + given event. + + The basic work here is sorting the result of find_raw_matches + """ + + # remove exclusions + matches = filter(lambda template: template not in self.exclude, + self.find_raw_matches(event)) + + # sort the results according to the order they were added + return sorted(matches, key=self.match_order.index) def __nonzero__(self): """ @@ -118,16 +173,6 @@ """ return bool(self.tag_templates or self.other_templates) - def __iter__(self): - """ - I don't think we really need this, but it lets us behave like a list - """ - for template_list in self.tag_templates.iteritems(): - for template in template_list: - yield template - for template in self.other_templates: - yield template - def __str__(self): parent = "" if self.parent: