Index: roundup/cgi/templating.py
===================================================================
--- roundup/cgi/templating.py	(Revision 4543)
+++ roundup/cgi/templating.py	(Arbeitskopie)
@@ -27,6 +27,8 @@
 from roundup import i18n
 from roundup.i18n import _
 
+from KeywordsExpr import render_keywords_expression_editor
+
 try:
     import cPickle as pickle
 except ImportError:
@@ -2848,6 +2850,9 @@
             raise AttributeError, name
         return self.client.instance.templating_utils[name]
 
+    def keywords_expressions(self, request):
+        return render_keywords_expression_editor(request)
+
     def html_calendar(self, request):
         """Generate a HTML calendar.
 
Index: roundup/cgi/KeywordsExpr.py
===================================================================
--- roundup/cgi/KeywordsExpr.py	(Revision 0)
+++ roundup/cgi/KeywordsExpr.py	(Revision 0)
@@ -0,0 +1,273 @@
+# This module is free software, you may redistribute it
+# and/or modify under the same terms as Python.
+
+WINDOW_CONTENT = '''\
+
Keyword Expression Editor:
+
+
+
+'''
+
+def list_nodes(request):
+    prop = request.form.getfirst("property")
+    cls = request.client.db.getclass(prop)
+    items = []
+    for nodeid in cls.getnodeids():
+        l = cls.getnode(nodeid).items()
+        l = dict([x for x in l if len(x) == 2])
+        try:
+            items.append((l['id'], l['name']))
+        except KeyError:
+            pass
+    items.sort(key=lambda x: int(x[0]))
+    return items
+
+def items_to_keywords(items):
+    return ',\n    '.join(['["%s", "%s"]' % x for x in items])
+   
+
+def render_keywords_expression_editor(request):
+    prop = request.form.getfirst("property")
+
+    window_content = WINDOW_CONTENT % {
+        'prop'    : prop,
+        'keywords': items_to_keywords(list_nodes(request)),
+        'original': ''
+    }
+
+    return window_content
+
+# vim: set et sts=4 sw=4 :
Index: roundup/backends/back_anydbm.py
===================================================================
--- roundup/backends/back_anydbm.py	(Revision 4543)
+++ roundup/backends/back_anydbm.py	(Arbeitskopie)
@@ -49,6 +49,60 @@
 def db_nuke(config):
     shutil.rmtree(config.DATABASE)
 
+class Equals:
+    def __init__(self, x):
+        self.x = x
+
+    def evaluate(self, v):
+        return self.x in v
+
+class Not:
+    def __init__(self, x):
+        self.x = x
+
+    def evaluate(self, v):
+        return not self.x.evaluate(v)
+
+class Or:
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+    def evaluate(self, v):
+        return self.x.evaluate(v) or self.y.evaluate(v)
+
+class And:
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+    def evaluate(self, v):
+        return self.x.evaluate(v) and self.y.evaluate(v)
+        
+
+class Expression:
+
+    def __init__(self, v):
+        try:
+            opcodes = [int(x) for x in v]
+            if min(opcodes) >= -1: raise ValueError()
+
+            stack = []
+            for opcode in opcodes:
+                if opcode == -2: # not
+                    stack.append(Not(stack.pop()))
+                elif opcode == -3: # and
+                    stack.append(And(stack.pop(), stack.pop()))
+                elif opcode == -4: # or
+                    stack.append(Or(stack.pop(), stack.pop()))
+                else: # =
+                    stack.append(Equals(opcode))
+
+            compiled = stack.pop()
+            self.evaluate = lambda x: compiled.evaluate([int(y) for y in x])
+        except:
+            self.evaluate = lambda x: bool(set(x) & set(v))
+
 #
 # Now the database
 #
@@ -1700,12 +1754,10 @@
                         if not v:
                             match = not nv
                         else:
-                            # othewise, make sure this node has each of the
+                            # otherwise, make sure this node has each of the
                             # required values
-                            for want in v:
-                                if want in nv:
-                                    match = 1
-                                    break
+                            expr = Expression(v)
+                            if expr.evaluate(nv): match = 1
                     elif t == STRING:
                         if nv is None:
                             nv = ''
Index: share/roundup/templates/devel/html/page.html
===================================================================
--- share/roundup/templates/devel/html/page.html	(Revision 4543)
+++ share/roundup/templates/devel/html/page.html	(Arbeitskopie)
@@ -307,6 +307,20 @@
   
 
 
++  
+  (edit)
++
 
Index: share/roundup/templates/classic/html/_generic.keywords_expr.html
===================================================================
--- share/roundup/templates/classic/html/_generic.keywords_expr.html	(Revision 0)
+++ share/roundup/templates/classic/html/_generic.keywords_expr.html	(Revision 0)
@@ -0,0 +1,11 @@
+
+
+ 
+  
+  
+  
+ 
+ 
+ 
+
Index: share/roundup/templates/classic/html/page.html
===================================================================
--- share/roundup/templates/classic/html/page.html	(Revision 4543)
+++ share/roundup/templates/classic/html/page.html	(Arbeitskopie)
@@ -247,6 +247,22 @@
   
 
 
+ | + +
 
Index: share/roundup/templates/classic/html/issue.search.html
===================================================================
--- share/roundup/templates/classic/html/issue.search.html	(Revision 4543)
+++ share/roundup/templates/classic/html/issue.search.html	(Arbeitskopie)
@@ -23,6 +23,7 @@
    sort_input templates/page/macros/sort_input;
    group_input templates/page/macros/group_input;
    search_select templates/page/macros/search_select;
+   search_select_keywords templates/page/macros/search_select_keywords;
    search_select_translated templates/page/macros/search_select_translated;
    search_multiselect templates/page/macros/search_multiselect;">
 
@@ -54,7 +55,7 @@
                 db_klass string:keyword;
                 db_content string:name;">+
+     
+    (expr) 
+   | Keyword:- | + |  |