Index: trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 4545)
+++ trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 4546)
@@ -69,7 +69,6 @@
         protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) {
             for (OsmPrimitive p : primitives) {
-                if (match(p)) {
+                if (match(p))
                     return true;
-                }
             }
             return false;
@@ -81,7 +80,6 @@
         protected boolean forallMatch(Collection<? extends OsmPrimitive> primitives) {
             for (OsmPrimitive p : primitives) {
-                if (!match(p)) {
+                if (!match(p))
                     return false;
-                }
             }
             return true;
@@ -110,4 +108,7 @@
         }
         @Override public String toString() {return "!"+match;}
+        public Match getMatch() {
+            return match;
+        }
     }
 
@@ -130,7 +131,7 @@
     }
 
-    private static class And extends Match {
-        private Match lhs;
-        private Match rhs;
+    public static class And extends Match {
+        private final Match lhs;
+        private final Match rhs;
         public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
         @Override public boolean match(OsmPrimitive osm) {
@@ -138,9 +139,15 @@
         }
         @Override public String toString() {return lhs+" && "+rhs;}
-    }
-
-    private static class Or extends Match {
-        private Match lhs;
-        private Match rhs;
+        public Match getLhs() {
+            return lhs;
+        }
+        public Match getRhs() {
+            return rhs;
+        }
+    }
+
+    public static class Or extends Match {
+        private final Match lhs;
+        private final Match rhs;
         public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
         @Override public boolean match(OsmPrimitive osm) {
@@ -148,4 +155,10 @@
         }
         @Override public String toString() {return lhs+" || "+rhs;}
+        public Match getLhs() {
+            return lhs;
+        }
+        public Match getRhs() {
+            return rhs;
+        }
     }
 
@@ -552,9 +565,8 @@
         public boolean match(OsmPrimitive osm) {
             Integer count = getCount(osm);
-            if (count == null) {
+            if (count == null)
                 return false;
-            } else {
+            else
                 return (count >= minCount) && (count <= maxCount);
-            }
         }
 
@@ -575,9 +587,8 @@
         @Override
         protected Integer getCount(OsmPrimitive osm) {
-            if (!(osm instanceof Way)) {
+            if (!(osm instanceof Way))
                 return null;
-            } else {
+            else
                 return ((Way) osm).getNodesCount();
-            }
         }
 
@@ -649,15 +660,17 @@
     }
 
-    private static class Parent extends Match {
-        private Match child;
-        public Parent(Match m) { child = m; }
+    public static class Parent extends Match {
+        private final Match child;
+        public Parent(Match m) {
+            if (m == null) {
+                // "parent" (null) should mean the same as "parent()"
+                // (Always). I.e. match everything
+                child = new Always();
+            } else {
+                child = m;
+            }
+        }
         @Override public boolean match(OsmPrimitive osm) {
             boolean isParent = false;
-
-            // "parent" (null) should mean the same as "parent()"
-            // (Always). I.e. match everything
-            if (child == null) {
-                child = new Always();
-            }
 
             if (osm instanceof Way) {
@@ -673,7 +686,10 @@
         }
         @Override public String toString() {return "parent(" + child + ")";}
-    }
-
-    private static class Child extends Match {
+        public Match getChild() {
+            return child;
+        }
+    }
+
+    public static class Child extends Match {
         private final Match parent;
 
@@ -696,9 +712,13 @@
         }
         @Override public String toString() {return "child(" + parent + ")";}
-    }
-    
+
+        public Match getParent() {
+            return parent;
+        }
+    }
+
     /**
      * Matches on the area of a closed way.
-     * 
+     *
      * @author Ole Jørgen Brønner
      */
@@ -711,7 +731,6 @@
         @Override
         protected Integer getCount(OsmPrimitive osm) {
-            if (!(osm instanceof Way && ((Way) osm).isClosed())) {
+            if (!(osm instanceof Way && ((Way) osm).isClosed()))
                 return null;
-            }
             Way way = (Way) osm;
             return (int) Geometry.closedWayArea(way);
@@ -743,9 +762,9 @@
         @Override
         public boolean match(OsmPrimitive osm) {
-            if (!osm.isUsable()) {
+            if (!osm.isUsable())
                 return false;
-            } else if (osm instanceof Node) {
+            else if (osm instanceof Node)
                 return bounds.contains(((Node) osm).getCoor());
-            } else if (osm instanceof Way) {
+            else if (osm instanceof Way) {
                 Collection<Node> nodes = ((Way) osm).getNodes();
                 return all ? forallMatch(nodes) : existsMatch(nodes);
@@ -753,7 +772,6 @@
                 Collection<OsmPrimitive> primitives = ((Relation) osm).getMemberPrimitives();
                 return all ? forallMatch(primitives) : existsMatch(primitives);
-            } else {
+            } else
                 return false;
-            }
         }
     }
@@ -799,5 +817,5 @@
 
     public static Match compile(String searchStr, boolean caseSensitive, boolean regexSearch)
-    throws ParseError {
+            throws ParseError {
         return new SearchCompiler(caseSensitive, regexSearch,
                 new PushbackTokenizer(
Index: trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java	(revision 4546)
+++ trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java	(revision 4546)
@@ -0,0 +1,266 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools.template_engine;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.openstreetmap.josm.actions.search.SearchCompiler.And;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Child;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Not;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Or;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Parent;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
+import org.openstreetmap.josm.data.osm.Way;
+
+public class ContextSwitchTemplate implements TemplateEntry {
+
+    private static final TemplateEngineDataProvider EMTPY_PROVIDER = new TemplateEngineDataProvider() {
+        @Override
+        public Object getTemplateValue(String name, boolean special) {
+            return null;
+        }
+
+        @Override
+        public Collection<String> getTemplateKeys() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean evaluateCondition(Match condition) {
+            return false;
+        }
+    };
+
+    private abstract class ContextProvider extends Match {
+        Match condition;
+        abstract List<OsmPrimitive> getPrimitives(OsmPrimitive root);
+    }
+
+    private class ParentSet extends ContextProvider {
+        private final Match childCondition;
+
+        ParentSet(Match child) {
+            this.childCondition = child;
+        }
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            throw new UnsupportedOperationException();
+        }
+        @Override
+        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
+            List<OsmPrimitive> children;
+            if (childCondition instanceof ContextProvider) {
+                children = ((ContextProvider) childCondition).getPrimitives(root);
+            } else if (childCondition.match(root)) {
+                children = Collections.singletonList(root);
+            } else {
+                children = Collections.emptyList();
+            }
+
+            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
+            for (OsmPrimitive child: children) {
+                for (OsmPrimitive parent: child.getReferrers()) {
+                    if (condition == null || condition.match(parent)) {
+                        result.add(parent);
+                    }
+                }
+            }
+            return result;
+        }
+    }
+
+    private class ChildSet extends ContextProvider {
+        private final Match parentCondition;
+
+        ChildSet(Match parentCondition) {
+            this.parentCondition = parentCondition;
+        }
+
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
+            List<OsmPrimitive> parents;
+            if (parentCondition instanceof ContextProvider) {
+                parents = ((ContextProvider) parentCondition).getPrimitives(root);
+            } else if (parentCondition.match(root)) {
+                parents = Collections.singletonList(root);
+            } else {
+                parents = Collections.emptyList();
+            }
+            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
+            for (OsmPrimitive p: parents) {
+                if (p instanceof Way) {
+                    for (Node n: ((Way) p).getNodes()) {
+                        if (condition != null && condition.match(n)) {
+                            result.add(n);
+                        }
+                        result.add(n);
+                    }
+                } else if (p instanceof Relation) {
+                    for (RelationMember rm: ((Relation) p).getMembers()) {
+                        if (condition != null && condition.match(rm.getMember())) {
+                            result.add(rm.getMember());
+                        }
+                    }
+                }
+            }
+            return result;
+        }
+    }
+
+    private class OrSet extends ContextProvider {
+        private final ContextProvider lhs;
+        private final ContextProvider rhs;
+
+        OrSet(ContextProvider lhs, ContextProvider rhs) {
+            this.lhs = lhs;
+            this.rhs = rhs;
+        }
+
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
+            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
+            for (OsmPrimitive o: lhs.getPrimitives(root)) {
+                if (condition == null || condition.match(o)) {
+                    result.add(o);
+                }
+            }
+            for (OsmPrimitive o: rhs.getPrimitives(root)) {
+                if (condition == null || condition.match(o) && !result.contains(o)) {
+                    result.add(o);
+                }
+            }
+            return result;
+        }
+    }
+
+    private class AndSet extends ContextProvider {
+        private final ContextProvider lhs;
+        private final ContextProvider rhs;
+
+        AndSet(ContextProvider lhs, ContextProvider rhs) {
+            this.lhs = lhs;
+            this.rhs = rhs;
+        }
+
+        @Override
+        public boolean match(OsmPrimitive osm) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        List<OsmPrimitive> getPrimitives(OsmPrimitive root) {
+            List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
+            List<OsmPrimitive> lhsList = lhs.getPrimitives(root);
+            for (OsmPrimitive o: rhs.getPrimitives(root)) {
+                if (lhsList.contains(o) && condition == null && condition.match(o)) {
+                    result.add(o);
+                }
+            }
+            return result;
+        }
+    }
+
+    private final ContextProvider context;
+    private final TemplateEntry template;
+
+    private Match transform(Match m, int searchExpressionPosition) throws ParseError {
+        if (m instanceof Parent) {
+            Match child = transform(((Parent) m).getChild(), searchExpressionPosition);
+            return new ParentSet(child);
+        } else if (m instanceof Child) {
+            Match parent = transform(((Child) m).getParent(), searchExpressionPosition);
+            return new ChildSet(parent);
+        } else if (m instanceof And) {
+            Match lhs = transform(((And) m).getLhs(), searchExpressionPosition);
+            Match rhs = transform(((And) m).getRhs(), searchExpressionPosition);
+
+            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
+                return new AndSet((ContextProvider)lhs, (ContextProvider)rhs);
+            else if (lhs instanceof ContextProvider) {
+                ContextProvider cp = (ContextProvider) lhs;
+                if (cp.condition == null) {
+                    cp.condition = rhs;
+                } else {
+                    cp.condition = new And(cp.condition, rhs);
+                }
+                return cp;
+            } else if (rhs instanceof ContextProvider) {
+                ContextProvider cp = (ContextProvider) rhs;
+                if (cp.condition == null) {
+                    cp.condition = lhs;
+                } else {
+                    cp.condition = new And(lhs, cp.condition);
+                }
+                return cp;
+            } else
+                return m;
+        } else if (m instanceof Or) {
+            Match lhs = transform(((Or) m).getLhs(), searchExpressionPosition);
+            Match rhs = transform(((Or) m).getRhs(), searchExpressionPosition);
+
+            if (lhs instanceof ContextProvider && rhs instanceof ContextProvider)
+                return new OrSet((ContextProvider)lhs, (ContextProvider)rhs);
+            else if (lhs instanceof ContextProvider)
+                throw new ParseError(tr("Error in search expression on position {0} - right side of or(|) expression must return set of primitives", searchExpressionPosition));
+            else if (rhs instanceof ContextProvider)
+                throw new ParseError(tr("Error in search expression on position {0} - left side of or(|) expression must return set of primitives", searchExpressionPosition));
+            else
+                return m;
+        } else if (m instanceof Not) {
+            Match match = transform(((Not) m).getMatch(), searchExpressionPosition);
+            if (match instanceof ContextProvider)
+                throw new ParseError(tr("Error in search expression on position {0} - not(-) cannot be used in this context", searchExpressionPosition));
+            else
+                return m;
+        } else
+            return m;
+    }
+
+    public ContextSwitchTemplate(Match match, TemplateEntry template, int searchExpressionPosition) throws ParseError {
+        Match m = transform(match, searchExpressionPosition);
+        if (!(m instanceof ContextProvider))
+            throw new ParseError(tr("Error in search expression on position {0} - expression must return different then current primitive", searchExpressionPosition));
+        else {
+            context = (ContextProvider) m;
+        }
+        this.template = template;
+    }
+
+    @Override
+    public void appendText(StringBuilder result, TemplateEngineDataProvider dataProvider) {
+        List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
+        if (primitives != null && !primitives.isEmpty()) {
+            template.appendText(result, primitives.get(0));
+        } else {
+            template.appendText(result, EMTPY_PROVIDER);
+        }
+    }
+
+    @Override
+    public boolean isValid(TemplateEngineDataProvider dataProvider) {
+        List<OsmPrimitive> primitives = context.getPrimitives((OsmPrimitive) dataProvider);
+        if (primitives != null && !primitives.isEmpty())
+            return template.isValid(primitives.get(0));
+        else
+            return false;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/tools/template_engine/ParseError.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/template_engine/ParseError.java	(revision 4545)
+++ trunk/src/org/openstreetmap/josm/tools/template_engine/ParseError.java	(revision 4546)
@@ -21,6 +21,11 @@
     }
 
-    public ParseError(org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
-        super(tr("Error while parsing search expression"), e);
+    public ParseError(int position, org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
+        super(tr("Error while parsing search expression on position {0}", position), e);
+        unexpectedToken = null;
+    }
+
+    public ParseError(String message) {
+        super(message);
         unexpectedToken = null;
     }
@@ -29,3 +34,7 @@
         return unexpectedToken;
     }
+
+    public static ParseError unexpectedChar(char expected, char found, int position) {
+        return new ParseError(tr("Unexpected char on {0}. Expected {1} found {2}", position, expected, found));
+    }
 }
Index: trunk/src/org/openstreetmap/josm/tools/template_engine/TemplateParser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/template_engine/TemplateParser.java	(revision 4545)
+++ trunk/src/org/openstreetmap/josm/tools/template_engine/TemplateParser.java	(revision 4546)
@@ -2,4 +2,6 @@
 package org.openstreetmap.josm.tools.template_engine;
 
+
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.util.ArrayList;
@@ -9,4 +11,5 @@
 
 import org.openstreetmap.josm.actions.search.SearchCompiler;
+import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
 import org.openstreetmap.josm.tools.template_engine.Tokenizer.Token;
 import org.openstreetmap.josm.tools.template_engine.Tokenizer.TokenType;
@@ -42,4 +45,6 @@
             if (token.getType() == TokenType.CONDITION_START) {
                 templateEntry = parseCondition();
+            } else if (token.getType() == TokenType.CONTEXT_SWITCH_START) {
+                templateEntry = parseContextSwitch();
             } else if (token.getType() == TokenType.VARIABLE_START) {
                 templateEntry = parseVariable();
@@ -63,5 +68,5 @@
     }
 
-    private void skipWhitespace() {
+    private void skipWhitespace() throws ParseError {
         Token token = tokenizer.lookAhead();
         if (token.getType() == TokenType.TEXT && token.getText().trim().isEmpty()) {
@@ -76,15 +81,15 @@
 
             TemplateEntry condition;
-            String searchExpression = tokenizer.skip('\'');
+            Token searchExpression = tokenizer.skip('\'');
             check(TokenType.APOSTROPHE);
             condition = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS);
             check(TokenType.APOSTROPHE);
-            if (searchExpression.trim().isEmpty()) {
+            if (searchExpression.getText().trim().isEmpty()) {
                 result.getEntries().add(condition);
             } else {
                 try {
-                    result.getEntries().add(new SearchExpressionCondition(SearchCompiler.compile(searchExpression, false, false), condition));
+                    result.getEntries().add(new SearchExpressionCondition(SearchCompiler.compile(searchExpression.getText(), false, false), condition));
                 } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
-                    throw new ParseError(e);
+                    throw new ParseError(searchExpression.getPosition(), e);
                 }
             }
@@ -101,3 +106,26 @@
     }
 
+    private TemplateEntry parseContextSwitch() throws ParseError {
+
+        check(TokenType.CONTEXT_SWITCH_START);
+        Token searchExpression = tokenizer.skip('\'');
+        check(TokenType.APOSTROPHE);
+        TemplateEntry template = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS);
+        check(TokenType.APOSTROPHE);
+        ContextSwitchTemplate result;
+        if (searchExpression.getText().trim().isEmpty())
+            throw new ParseError(tr("Expected search expression"));
+        else {
+            try {
+                Match match = SearchCompiler.compile(searchExpression.getText(), false, false);
+                result = new ContextSwitchTemplate(match, template, searchExpression.getPosition());
+            } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
+                throw new ParseError(searchExpression.getPosition(), e);
+            }
+        }
+        skipWhitespace();
+        check(TokenType.END);
+        return result;
+    }
+
 }
Index: trunk/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java	(revision 4545)
+++ trunk/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java	(revision 4546)
@@ -40,5 +40,5 @@
     }
 
-    public enum TokenType { CONDITION_START, VARIABLE_START, END, PIPE, APOSTROPHE, TEXT, EOF }
+    public enum TokenType { CONDITION_START, VARIABLE_START, CONTEXT_SWITCH_START, END, PIPE, APOSTROPHE, TEXT, EOF }
 
     private final List<Character> specialCharaters = Arrays.asList(new Character[] {'$', '?', '{', '}', '|', '\''});
@@ -64,5 +64,5 @@
     }
 
-    public Token nextToken() {
+    public Token nextToken() throws ParseError {
         if (currentToken != null) {
             Token result = currentToken;
@@ -79,5 +79,4 @@
             getChar();
             return new Token(TokenType.VARIABLE_START, position);
-
         case '?':
             getChar();
@@ -86,5 +85,12 @@
                 return new Token(TokenType.CONDITION_START, position);
             } else
-                throw new AssertionError();
+                throw ParseError.unexpectedChar('{', (char)c, position);
+        case '!':
+            getChar();
+            if (c == '{') {
+                getChar();
+                return new Token(TokenType.CONTEXT_SWITCH_START, position);
+            } else
+                throw ParseError.unexpectedChar('{', (char)c, position);
         case '}':
             getChar();
@@ -111,5 +117,5 @@
     }
 
-    public Token lookAhead() {
+    public Token lookAhead() throws ParseError {
         if (currentToken == null) {
             currentToken = nextToken();
@@ -118,6 +124,7 @@
     }
 
-    public String skip(char lastChar) {
+    public Token skip(char lastChar) {
         currentToken = null;
+        int position = index;
         StringBuilder result = new StringBuilder();
         while (c != lastChar && c != -1) {
@@ -128,5 +135,5 @@
             getChar();
         }
-        return result.toString();
+        return new Token(TokenType.TEXT, position, result.toString());
     }
 
Index: trunk/test/unit/org/openstreetmap/josm/tools/template_engine/TemplateEngineTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/tools/template_engine/TemplateEngineTest.java	(revision 4545)
+++ trunk/test/unit/org/openstreetmap/josm/tools/template_engine/TemplateEngineTest.java	(revision 4546)
@@ -13,5 +13,8 @@
 import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
 import org.openstreetmap.josm.data.Preferences;
+import org.openstreetmap.josm.data.osm.DatasetFactory;
+import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.RelationMember;
 import org.unitils.reflectionassert.ReflectionAssert;
 
@@ -150,7 +153,132 @@
         templateEntry.appendText(sb, dataProvider);
         Assert.assertEquals("waypointNameulocalNameuspecialKey", sb.toString());
-
-    }
-
+    }
+
+    @Test
+    public void testSearchExpression() throws Exception {
+        Match match = compile("(parent type=type1 type=parent1) | (parent type=type2 type=parent2)");
+        //"parent(type=type1,type=parent1) | (parent(type=type2,type=parent2)"
+        //TODO
+    }
+
+    @Test
+    public void testSwitchContext() throws Exception {
+        TemplateParser parser = new TemplateParser("!{parent() type=parent2 '{name}'}");
+        DatasetFactory ds = new DatasetFactory();
+        Relation parent1 = ds.addRelation(1);
+        parent1.put("type", "parent1");
+        parent1.put("name", "name_parent1");
+        Relation parent2 = ds.addRelation(2);
+        parent2.put("type", "parent2");
+        parent2.put("name", "name_parent2");
+        Node child = ds.addNode(1);
+        parent1.addMember(new RelationMember("", child));
+        parent2.addMember(new RelationMember("", child));
+
+        StringBuilder sb = new StringBuilder();
+        TemplateEntry entry = parser.parse();
+        entry.appendText(sb, child);
+
+        Assert.assertEquals("name_parent2", sb.toString());
+    }
+
+    @Test
+    public void testSetOr() throws ParseError {
+        TemplateParser parser = new TemplateParser("!{(parent(type=type1) type=parent1) | (parent type=type2 type=parent2) '{name}'}");
+        DatasetFactory ds = new DatasetFactory();
+        Relation parent1 = ds.addRelation(1);
+        parent1.put("type", "parent1");
+        parent1.put("name", "name_parent1");
+        Relation parent2 = ds.addRelation(2);
+        parent2.put("type", "parent2");
+        parent2.put("name", "name_parent2");
+        Node child1 = ds.addNode(1);
+        child1.put("type", "type1");
+        parent1.addMember(new RelationMember("", child1));
+        parent2.addMember(new RelationMember("", child1));
+        Node child2 = ds.addNode(2);
+        child2.put("type", "type2");
+        parent1.addMember(new RelationMember("", child2));
+        parent2.addMember(new RelationMember("", child2));
+
+
+        StringBuilder sb = new StringBuilder();
+        TemplateEntry entry = parser.parse();
+        entry.appendText(sb, child1);
+        entry.appendText(sb, child2);
+
+        Assert.assertEquals("name_parent1name_parent2", sb.toString());
+    }
+
+    @Test
+    public void testMultilevel() throws ParseError {
+        TemplateParser parser = new TemplateParser("!{(parent(parent(type=type1)) type=grandparent) | (parent type=type2 type=parent2) '{name}'}");
+        DatasetFactory ds = new DatasetFactory();
+        Relation parent1 = ds.addRelation(1);
+        parent1.put("type", "parent1");
+        parent1.put("name", "name_parent1");
+        Relation parent2 = ds.addRelation(2);
+        parent2.put("type", "parent2");
+        parent2.put("name", "name_parent2");
+        Node child1 = ds.addNode(1);
+        child1.put("type", "type1");
+        parent1.addMember(new RelationMember("", child1));
+        parent2.addMember(new RelationMember("", child1));
+        Node child2 = ds.addNode(2);
+        child2.put("type", "type2");
+        parent1.addMember(new RelationMember("", child2));
+        parent2.addMember(new RelationMember("", child2));
+        Relation grandParent = ds.addRelation(3);
+        grandParent.put("type", "grandparent");
+        grandParent.put("name", "grandparent_name");
+        grandParent.addMember(new RelationMember("", parent1));
+
+
+        StringBuilder sb = new StringBuilder();
+        TemplateEntry entry = parser.parse();
+        entry.appendText(sb, child1);
+        entry.appendText(sb, child2);
+
+        Assert.assertEquals("grandparent_namename_parent2", sb.toString());
+    }
+
+    @Test(expected=ParseError.class)
+    public void testErrorsNot() throws ParseError {
+        TemplateParser parser = new TemplateParser("!{-parent() '{name}'}");
+        parser.parse();
+    }
+
+    @Test(expected=ParseError.class)
+    public void testErrorOr() throws ParseError {
+        TemplateParser parser = new TemplateParser("!{parent() | type=type1 '{name}'}");
+        parser.parse();
+    }
+
+    @Test
+    public void testChild() throws ParseError {
+        TemplateParser parser = new TemplateParser("!{((child(type=type1) type=child1) | (child type=type2 type=child2)) type=child2 '{name}'}");
+        DatasetFactory ds = new DatasetFactory();
+        Relation parent1 = ds.addRelation(1);
+        parent1.put("type", "type1");
+        Relation parent2 = ds.addRelation(2);
+        parent2.put("type", "type2");
+        Node child1 = ds.addNode(1);
+        child1.put("type", "child1");
+        child1.put("name", "child1");
+        parent1.addMember(new RelationMember("", child1));
+        parent2.addMember(new RelationMember("", child1));
+        Node child2 = ds.addNode(2);
+        child2.put("type", "child2");
+        child2.put("name", "child2");
+        parent1.addMember(new RelationMember("", child2));
+        parent2.addMember(new RelationMember("", child2));
+
+
+        StringBuilder sb = new StringBuilder();
+        TemplateEntry entry = parser.parse();
+        entry.appendText(sb, parent2);
+
+        Assert.assertEquals("child2", sb.toString());
+    }
 
 }
