Test new approach with AST (AI assisted)
diff --git a/.gitignore b/.gitignore
index d654485..9aa3b8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 testdata/sandbox
+cmd/termmapper
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 92a0861..edf6152 100644
--- a/go.mod
+++ b/go.mod
@@ -1,21 +1,11 @@
-module github.com/KorAP/KoralPipe-TermMapper
+module github.com/KorAP/KoralPipe-TermMapper2
 
 go 1.22.2
 
-require (
-	github.com/rs/zerolog v1.34.0
-	github.com/stretchr/testify v1.10.0
-	github.com/tidwall/gjson v1.18.0
-	github.com/tidwall/sjson v1.2.5
-)
+require github.com/stretchr/testify v1.10.0
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.19 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/tidwall/match v1.1.1 // indirect
-	github.com/tidwall/pretty v1.2.0 // indirect
-	golang.org/x/sys v0.12.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 9abbe28..713a0b4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,33 +1,9 @@
-github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
-github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
-github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
-github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
-github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
-github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
-github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
-github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go
new file mode 100644
index 0000000..aac7016
--- /dev/null
+++ b/pkg/ast/ast.go
@@ -0,0 +1,73 @@
+package ast
+
+// NodeType represents the type of a node in the AST
+type NodeType string
+
+const (
+	TokenNode     NodeType = "token"
+	TermGroupNode NodeType = "termGroup"
+	TermNode      NodeType = "term"
+)
+
+// RelationType represents the type of relation between nodes
+type RelationType string
+
+const (
+	AndRelation RelationType = "and"
+	OrRelation  RelationType = "or"
+)
+
+// MatchType represents the type of match operation
+type MatchType string
+
+const (
+	MatchEqual    MatchType = "eq"
+	MatchNotEqual MatchType = "ne"
+)
+
+// Node represents a node in the AST
+type Node interface {
+	Type() NodeType
+}
+
+// Token represents a token node in the query
+type Token struct {
+	Wrap Node `json:"wrap"`
+}
+
+func (t *Token) Type() NodeType {
+	return TokenNode
+}
+
+// TermGroup represents a group of terms with a relation
+type TermGroup struct {
+	Operands []Node       `json:"operands"`
+	Relation RelationType `json:"relation"`
+}
+
+func (tg *TermGroup) Type() NodeType {
+	return TermGroupNode
+}
+
+// Term represents a terminal node with matching criteria
+type Term struct {
+	Foundry string    `json:"foundry"`
+	Key     string    `json:"key"`
+	Layer   string    `json:"layer"`
+	Match   MatchType `json:"match"`
+	Value   string    `json:"value,omitempty"`
+}
+
+func (t *Term) Type() NodeType {
+	return TermNode
+}
+
+// Pattern represents a pattern to match in the AST
+type Pattern struct {
+	Root Node
+}
+
+// Replacement represents a replacement pattern
+type Replacement struct {
+	Root Node
+}
diff --git a/pkg/ast/ast_test.go b/pkg/ast/ast_test.go
new file mode 100644
index 0000000..1a01d0b
--- /dev/null
+++ b/pkg/ast/ast_test.go
@@ -0,0 +1,190 @@
+package ast
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNodeTypes(t *testing.T) {
+	tests := []struct {
+		name     string
+		node     Node
+		expected NodeType
+	}{
+		{
+			name:     "Token node returns correct type",
+			node:     &Token{Wrap: &Term{}},
+			expected: TokenNode,
+		},
+		{
+			name: "TermGroup node returns correct type",
+			node: &TermGroup{
+				Operands: []Node{&Term{}},
+				Relation: AndRelation,
+			},
+			expected: TermGroupNode,
+		},
+		{
+			name: "Term node returns correct type",
+			node: &Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   MatchEqual,
+			},
+			expected: TermNode,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expected, tt.node.Type())
+		})
+	}
+}
+
+func TestTermGroupConstruction(t *testing.T) {
+	term1 := &Term{
+		Foundry: "opennlp",
+		Key:     "DET",
+		Layer:   "p",
+		Match:   MatchEqual,
+	}
+
+	term2 := &Term{
+		Foundry: "opennlp",
+		Key:     "AdjType",
+		Layer:   "m",
+		Match:   MatchEqual,
+		Value:   "Pdt",
+	}
+
+	group := &TermGroup{
+		Operands: []Node{term1, term2},
+		Relation: AndRelation,
+	}
+
+	assert.Len(t, group.Operands, 2)
+	assert.Equal(t, AndRelation, group.Relation)
+	assert.Equal(t, TermGroupNode, group.Type())
+
+	// Test operands are correctly set
+	assert.Equal(t, term1, group.Operands[0])
+	assert.Equal(t, term2, group.Operands[1])
+}
+
+func TestTokenConstruction(t *testing.T) {
+	term := &Term{
+		Foundry: "opennlp",
+		Key:     "DET",
+		Layer:   "p",
+		Match:   MatchEqual,
+	}
+
+	token := &Token{Wrap: term}
+
+	assert.Equal(t, TokenNode, token.Type())
+	assert.Equal(t, term, token.Wrap)
+}
+
+func TestTermConstruction(t *testing.T) {
+	tests := []struct {
+		name     string
+		term     *Term
+		foundry  string
+		key      string
+		layer    string
+		match    MatchType
+		hasValue bool
+		value    string
+	}{
+		{
+			name: "Term without value",
+			term: &Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   MatchEqual,
+			},
+			foundry:  "opennlp",
+			key:      "DET",
+			layer:    "p",
+			match:    MatchEqual,
+			hasValue: false,
+		},
+		{
+			name: "Term with value",
+			term: &Term{
+				Foundry: "opennlp",
+				Key:     "AdjType",
+				Layer:   "m",
+				Match:   MatchEqual,
+				Value:   "Pdt",
+			},
+			foundry:  "opennlp",
+			key:      "AdjType",
+			layer:    "m",
+			match:    MatchEqual,
+			hasValue: true,
+			value:    "Pdt",
+		},
+		{
+			name: "Term with not equal match",
+			term: &Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   MatchNotEqual,
+			},
+			foundry:  "opennlp",
+			key:      "DET",
+			layer:    "p",
+			match:    MatchNotEqual,
+			hasValue: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, TermNode, tt.term.Type())
+			assert.Equal(t, tt.foundry, tt.term.Foundry)
+			assert.Equal(t, tt.key, tt.term.Key)
+			assert.Equal(t, tt.layer, tt.term.Layer)
+			assert.Equal(t, tt.match, tt.term.Match)
+			if tt.hasValue {
+				assert.Equal(t, tt.value, tt.term.Value)
+			} else {
+				assert.Empty(t, tt.term.Value)
+			}
+		})
+	}
+}
+
+func TestPatternAndReplacement(t *testing.T) {
+	// Create a simple pattern
+	patternTerm := &Term{
+		Foundry: "opennlp",
+		Key:     "DET",
+		Layer:   "p",
+		Match:   MatchEqual,
+	}
+	pattern := Pattern{Root: patternTerm}
+
+	// Create a simple replacement
+	replacementTerm := &Term{
+		Foundry: "opennlp",
+		Key:     "COMBINED_DET",
+		Layer:   "p",
+		Match:   MatchEqual,
+	}
+	replacement := Replacement{Root: replacementTerm}
+
+	// Test pattern
+	assert.NotNil(t, pattern.Root)
+	assert.Equal(t, patternTerm, pattern.Root)
+
+	// Test replacement
+	assert.NotNil(t, replacement.Root)
+	assert.Equal(t, replacementTerm, replacement.Root)
+}
diff --git a/pkg/matcher/matcher.go b/pkg/matcher/matcher.go
new file mode 100644
index 0000000..46b4cc4
--- /dev/null
+++ b/pkg/matcher/matcher.go
@@ -0,0 +1,176 @@
+package matcher
+
+import (
+	"github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
+)
+
+// Matcher handles pattern matching and replacement in the AST
+type Matcher struct {
+	pattern     ast.Pattern
+	replacement ast.Replacement
+}
+
+// NewMatcher creates a new Matcher with the given pattern and replacement
+func NewMatcher(pattern ast.Pattern, replacement ast.Replacement) *Matcher {
+	return &Matcher{
+		pattern:     pattern,
+		replacement: replacement,
+	}
+}
+
+// Match checks if the given node matches the pattern
+func (m *Matcher) Match(node ast.Node) bool {
+	return m.matchNode(node, m.pattern.Root)
+}
+
+// Replace replaces all occurrences of the pattern in the given node with the replacement
+func (m *Matcher) Replace(node ast.Node) ast.Node {
+	if m.Match(node) {
+		return m.cloneNode(m.replacement.Root)
+	}
+
+	switch n := node.(type) {
+	case *ast.Token:
+		n.Wrap = m.Replace(n.Wrap)
+		return n
+
+	case *ast.TermGroup:
+		newOperands := make([]ast.Node, len(n.Operands))
+		for i, op := range n.Operands {
+			newOperands[i] = m.Replace(op)
+		}
+		n.Operands = newOperands
+		return n
+
+	default:
+		return node
+	}
+}
+
+// matchNode recursively checks if two nodes match
+func (m *Matcher) matchNode(node, pattern ast.Node) bool {
+	if pattern == nil {
+		return true
+	}
+	if node == nil {
+		return false
+	}
+
+	switch p := pattern.(type) {
+	case *ast.Token:
+		if t, ok := node.(*ast.Token); ok {
+			return m.matchNode(t.Wrap, p.Wrap)
+		}
+
+	case *ast.TermGroup:
+		// If we're matching against a term, try to match it against any operand
+		if t, ok := node.(*ast.Term); ok && p.Relation == ast.OrRelation {
+			for _, op := range p.Operands {
+				if m.matchNode(t, op) {
+					return true
+				}
+			}
+			return false
+		}
+
+		// If we're matching against a term group
+		if t, ok := node.(*ast.TermGroup); ok {
+			if t.Relation != p.Relation {
+				return false
+			}
+
+			if p.Relation == ast.OrRelation {
+				// For OR relation, at least one operand must match
+				for _, pOp := range p.Operands {
+					for _, tOp := range t.Operands {
+						if m.matchNode(tOp, pOp) {
+							return true
+						}
+					}
+				}
+				return false
+			}
+
+			// For AND relation, all pattern operands must match
+			if len(t.Operands) < len(p.Operands) {
+				return false
+			}
+
+			// Try to match pattern operands against node operands in any order
+			matched := make([]bool, len(t.Operands))
+			for _, pOp := range p.Operands {
+				found := false
+				for j, tOp := range t.Operands {
+					if !matched[j] && m.matchNode(tOp, pOp) {
+						matched[j] = true
+						found = true
+						break
+					}
+				}
+				if !found {
+					return false
+				}
+			}
+			return true
+		}
+
+	case *ast.Term:
+		// If we're matching against a term group with OR relation,
+		// try to match against any of its operands
+		if t, ok := node.(*ast.TermGroup); ok && t.Relation == ast.OrRelation {
+			for _, op := range t.Operands {
+				if m.matchNode(op, p) {
+					return true
+				}
+			}
+			return false
+		}
+
+		// Direct term to term matching
+		if t, ok := node.(*ast.Term); ok {
+			return t.Foundry == p.Foundry &&
+				t.Key == p.Key &&
+				t.Layer == p.Layer &&
+				t.Match == p.Match &&
+				(p.Value == "" || t.Value == p.Value)
+		}
+	}
+
+	return false
+}
+
+// cloneNode creates a deep copy of a node
+func (m *Matcher) cloneNode(node ast.Node) ast.Node {
+	if node == nil {
+		return nil
+	}
+
+	switch n := node.(type) {
+	case *ast.Token:
+		return &ast.Token{
+			Wrap: m.cloneNode(n.Wrap),
+		}
+
+	case *ast.TermGroup:
+		operands := make([]ast.Node, len(n.Operands))
+		for i, op := range n.Operands {
+			operands[i] = m.cloneNode(op)
+		}
+		return &ast.TermGroup{
+			Operands: operands,
+			Relation: n.Relation,
+		}
+
+	case *ast.Term:
+		return &ast.Term{
+			Foundry: n.Foundry,
+			Key:     n.Key,
+			Layer:   n.Layer,
+			Match:   n.Match,
+			Value:   n.Value,
+		}
+
+	default:
+		return nil
+	}
+}
diff --git a/pkg/matcher/matcher_test.go b/pkg/matcher/matcher_test.go
new file mode 100644
index 0000000..d058a7c
--- /dev/null
+++ b/pkg/matcher/matcher_test.go
@@ -0,0 +1,504 @@
+package matcher
+
+import (
+	"testing"
+
+	"github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMatchSimplePattern(t *testing.T) {
+	// Create a simple pattern: match a term with DET
+	pattern := ast.Pattern{
+		Root: &ast.Term{
+			Foundry: "opennlp",
+			Key:     "DET",
+			Layer:   "p",
+			Match:   ast.MatchEqual,
+		},
+	}
+
+	// Create a simple replacement
+	replacement := ast.Replacement{
+		Root: &ast.Term{
+			Foundry: "opennlp",
+			Key:     "COMBINED_DET",
+			Layer:   "p",
+			Match:   ast.MatchEqual,
+		},
+	}
+
+	m := NewMatcher(pattern, replacement)
+
+	tests := []struct {
+		name     string
+		input    ast.Node
+		expected bool
+	}{
+		{
+			name: "Exact match",
+			input: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+			expected: true,
+		},
+		{
+			name: "Different key",
+			input: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "NOUN",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+			expected: false,
+		},
+		{
+			name: "Different foundry",
+			input: &ast.Term{
+				Foundry: "different",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+			expected: false,
+		},
+		{
+			name: "Different match type",
+			input: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchNotEqual,
+			},
+			expected: false,
+		},
+		{
+			name: "Wrong node type",
+			input: &ast.Token{
+				Wrap: &ast.Term{
+					Foundry: "opennlp",
+					Key:     "DET",
+					Layer:   "p",
+					Match:   ast.MatchEqual,
+				},
+			},
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := m.Match(tt.input)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestMatchComplexPattern(t *testing.T) {
+	// Create a complex pattern: DET AND (AdjType=Pdt OR PronType=Ind)
+	pattern := ast.Pattern{
+		Root: &ast.Token{
+			Wrap: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "DET",
+						Layer:   "p",
+						Match:   ast.MatchEqual,
+					},
+					&ast.TermGroup{
+						Operands: []ast.Node{
+							&ast.Term{
+								Foundry: "opennlp",
+								Key:     "AdjType",
+								Layer:   "m",
+								Match:   ast.MatchEqual,
+								Value:   "Pdt",
+							},
+							&ast.Term{
+								Foundry: "opennlp",
+								Key:     "PronType",
+								Layer:   "m",
+								Match:   ast.MatchEqual,
+								Value:   "Ind",
+							},
+						},
+						Relation: ast.OrRelation,
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+		},
+	}
+
+	replacement := ast.Replacement{
+		Root: &ast.Token{
+			Wrap: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "COMBINED_DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+		},
+	}
+
+	m := NewMatcher(pattern, replacement)
+
+	tests := []struct {
+		name     string
+		input    ast.Node
+		expected bool
+	}{
+		{
+			name: "Match with AdjType=Pdt",
+			input: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "DET",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "AdjType",
+							Layer:   "m",
+							Match:   ast.MatchEqual,
+							Value:   "Pdt",
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "Match with PronType=Ind",
+			input: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "DET",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "PronType",
+							Layer:   "m",
+							Match:   ast.MatchEqual,
+							Value:   "Ind",
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "No match - missing DET",
+			input: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "NOUN",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "AdjType",
+							Layer:   "m",
+							Match:   ast.MatchEqual,
+							Value:   "Pdt",
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "No match - wrong value",
+			input: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "DET",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "AdjType",
+							Layer:   "m",
+							Match:   ast.MatchEqual,
+							Value:   "Wrong",
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := m.Match(tt.input)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestReplace(t *testing.T) {
+	// Create pattern and replacement
+	pattern := ast.Pattern{
+		Root: &ast.TermGroup{
+			Operands: []ast.Node{
+				&ast.Term{
+					Foundry: "opennlp",
+					Key:     "DET",
+					Layer:   "p",
+					Match:   ast.MatchEqual,
+				},
+				&ast.Term{
+					Foundry: "opennlp",
+					Key:     "AdjType",
+					Layer:   "m",
+					Match:   ast.MatchEqual,
+					Value:   "Pdt",
+				},
+			},
+			Relation: ast.AndRelation,
+		},
+	}
+
+	replacement := ast.Replacement{
+		Root: &ast.Term{
+			Foundry: "opennlp",
+			Key:     "COMBINED_DET",
+			Layer:   "p",
+			Match:   ast.MatchEqual,
+		},
+	}
+
+	m := NewMatcher(pattern, replacement)
+
+	tests := []struct {
+		name     string
+		input    ast.Node
+		expected ast.Node
+	}{
+		{
+			name: "Replace matching pattern",
+			input: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "DET",
+						Layer:   "p",
+						Match:   ast.MatchEqual,
+					},
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   ast.MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+			expected: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "COMBINED_DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+		},
+		{
+			name: "No replacement for non-matching pattern",
+			input: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "NOUN",
+						Layer:   "p",
+						Match:   ast.MatchEqual,
+					},
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   ast.MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+			expected: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "NOUN",
+						Layer:   "p",
+						Match:   ast.MatchEqual,
+					},
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   ast.MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+		},
+		{
+			name: "Replace in nested structure",
+			input: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.TermGroup{
+							Operands: []ast.Node{
+								&ast.Term{
+									Foundry: "opennlp",
+									Key:     "DET",
+									Layer:   "p",
+									Match:   ast.MatchEqual,
+								},
+								&ast.Term{
+									Foundry: "opennlp",
+									Key:     "AdjType",
+									Layer:   "m",
+									Match:   ast.MatchEqual,
+									Value:   "Pdt",
+								},
+							},
+							Relation: ast.AndRelation,
+						},
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "NOUN",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			expected: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "COMBINED_DET",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "NOUN",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := m.Replace(tt.input)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestMatchNodeOrder(t *testing.T) {
+	// Test that operands can match in any order
+	pattern := ast.Pattern{
+		Root: &ast.TermGroup{
+			Operands: []ast.Node{
+				&ast.Term{
+					Foundry: "opennlp",
+					Key:     "DET",
+					Layer:   "p",
+					Match:   ast.MatchEqual,
+				},
+				&ast.Term{
+					Foundry: "opennlp",
+					Key:     "AdjType",
+					Layer:   "m",
+					Match:   ast.MatchEqual,
+					Value:   "Pdt",
+				},
+			},
+			Relation: ast.AndRelation,
+		},
+	}
+
+	replacement := ast.Replacement{
+		Root: &ast.Term{
+			Foundry: "opennlp",
+			Key:     "COMBINED_DET",
+			Layer:   "p",
+			Match:   ast.MatchEqual,
+		},
+	}
+
+	m := NewMatcher(pattern, replacement)
+
+	// Test with operands in different orders
+	input1 := &ast.TermGroup{
+		Operands: []ast.Node{
+			&ast.Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+			&ast.Term{
+				Foundry: "opennlp",
+				Key:     "AdjType",
+				Layer:   "m",
+				Match:   ast.MatchEqual,
+				Value:   "Pdt",
+			},
+		},
+		Relation: ast.AndRelation,
+	}
+
+	input2 := &ast.TermGroup{
+		Operands: []ast.Node{
+			&ast.Term{
+				Foundry: "opennlp",
+				Key:     "AdjType",
+				Layer:   "m",
+				Match:   ast.MatchEqual,
+				Value:   "Pdt",
+			},
+			&ast.Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+		},
+		Relation: ast.AndRelation,
+	}
+
+	assert.True(t, m.Match(input1), "Should match with original order")
+	assert.True(t, m.Match(input2), "Should match with reversed order")
+}
diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go
new file mode 100644
index 0000000..be11da9
--- /dev/null
+++ b/pkg/parser/parser.go
@@ -0,0 +1,134 @@
+package parser
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
+)
+
+// rawNode represents the raw JSON structure
+type rawNode struct {
+	Type     string          `json:"@type"`
+	Wrap     json.RawMessage `json:"wrap,omitempty"`
+	Operands []rawNode       `json:"operands,omitempty"`
+	Relation string          `json:"relation,omitempty"`
+	Foundry  string          `json:"foundry,omitempty"`
+	Key      string          `json:"key,omitempty"`
+	Layer    string          `json:"layer,omitempty"`
+	Match    string          `json:"match,omitempty"`
+	Value    string          `json:"value,omitempty"`
+}
+
+// ParseJSON parses a JSON string into our AST representation
+func ParseJSON(data []byte) (ast.Node, error) {
+	var raw rawNode
+	if err := json.Unmarshal(data, &raw); err != nil {
+		return nil, fmt.Errorf("failed to parse JSON: %w", err)
+	}
+	return parseNode(raw)
+}
+
+// parseNode converts a raw node into an AST node
+func parseNode(raw rawNode) (ast.Node, error) {
+	switch raw.Type {
+	case "koral:token":
+		if raw.Wrap == nil {
+			return nil, fmt.Errorf("token node missing wrap field")
+		}
+		var wrapRaw rawNode
+		if err := json.Unmarshal(raw.Wrap, &wrapRaw); err != nil {
+			return nil, fmt.Errorf("failed to parse wrap: %w", err)
+		}
+		wrap, err := parseNode(wrapRaw)
+		if err != nil {
+			return nil, err
+		}
+		return &ast.Token{Wrap: wrap}, nil
+
+	case "koral:termGroup":
+		operands := make([]ast.Node, len(raw.Operands))
+		for i, op := range raw.Operands {
+			node, err := parseNode(op)
+			if err != nil {
+				return nil, err
+			}
+			operands[i] = node
+		}
+
+		relation := ast.AndRelation
+		if strings.HasSuffix(raw.Relation, "or") {
+			relation = ast.OrRelation
+		}
+
+		return &ast.TermGroup{
+			Operands: operands,
+			Relation: relation,
+		}, nil
+
+	case "koral:term":
+		match := ast.MatchEqual
+		if strings.HasSuffix(raw.Match, "ne") {
+			match = ast.MatchNotEqual
+		}
+
+		return &ast.Term{
+			Foundry: raw.Foundry,
+			Key:     raw.Key,
+			Layer:   raw.Layer,
+			Match:   match,
+			Value:   raw.Value,
+		}, nil
+
+	default:
+		return nil, fmt.Errorf("unknown node type: %s", raw.Type)
+	}
+}
+
+// SerializeToJSON converts an AST node back to JSON
+func SerializeToJSON(node ast.Node) ([]byte, error) {
+	raw := nodeToRaw(node)
+	return json.MarshalIndent(raw, "", "  ")
+}
+
+// nodeToRaw converts an AST node to a raw node for JSON serialization
+func nodeToRaw(node ast.Node) rawNode {
+	switch n := node.(type) {
+	case *ast.Token:
+		return rawNode{
+			Type: "koral:token",
+			Wrap: json.RawMessage(nodeToRaw(n.Wrap).toJSON()),
+		}
+
+	case *ast.TermGroup:
+		operands := make([]rawNode, len(n.Operands))
+		for i, op := range n.Operands {
+			operands[i] = nodeToRaw(op)
+		}
+		return rawNode{
+			Type:     "koral:termGroup",
+			Operands: operands,
+			Relation: "relation:" + string(n.Relation),
+		}
+
+	case *ast.Term:
+		return rawNode{
+			Type:    "koral:term",
+			Foundry: n.Foundry,
+			Key:     n.Key,
+			Layer:   n.Layer,
+			Match:   "match:" + string(n.Match),
+			Value:   n.Value,
+		}
+
+	default:
+		return rawNode{}
+	}
+}
+
+// toJSON converts a raw node to JSON bytes
+func (r rawNode) toJSON() []byte {
+	data, _ := json.Marshal(r)
+	return data
+}
diff --git a/pkg/parser/parser_test.go b/pkg/parser/parser_test.go
new file mode 100644
index 0000000..464c497
--- /dev/null
+++ b/pkg/parser/parser_test.go
@@ -0,0 +1,340 @@
+package parser
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestParseJSON(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    string
+		expected ast.Node
+		wantErr  bool
+	}{
+		{
+			name: "Parse simple term",
+			input: `{
+				"@type": "koral:term",
+				"foundry": "opennlp",
+				"key": "DET",
+				"layer": "p",
+				"match": "match:eq"
+			}`,
+			expected: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+			wantErr: false,
+		},
+		{
+			name: "Parse term group with AND relation",
+			input: `{
+				"@type": "koral:termGroup",
+				"operands": [
+					{
+						"@type": "koral:term",
+						"foundry": "opennlp",
+						"key": "DET",
+						"layer": "p",
+						"match": "match:eq"
+					},
+					{
+						"@type": "koral:term",
+						"foundry": "opennlp",
+						"key": "AdjType",
+						"layer": "m",
+						"match": "match:eq",
+						"value": "Pdt"
+					}
+				],
+				"relation": "relation:and"
+			}`,
+			expected: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "DET",
+						Layer:   "p",
+						Match:   ast.MatchEqual,
+					},
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   ast.MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+			wantErr: false,
+		},
+		{
+			name: "Parse token with wrapped term",
+			input: `{
+				"@type": "koral:token",
+				"wrap": {
+					"@type": "koral:term",
+					"foundry": "opennlp",
+					"key": "DET",
+					"layer": "p",
+					"match": "match:eq"
+				}
+			}`,
+			expected: &ast.Token{
+				Wrap: &ast.Term{
+					Foundry: "opennlp",
+					Key:     "DET",
+					Layer:   "p",
+					Match:   ast.MatchEqual,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "Parse complex nested structure",
+			input: `{
+				"@type": "koral:token",
+				"wrap": {
+					"@type": "koral:termGroup",
+					"operands": [
+						{
+							"@type": "koral:term",
+							"foundry": "opennlp",
+							"key": "DET",
+							"layer": "p",
+							"match": "match:eq"
+						},
+						{
+							"@type": "koral:termGroup",
+							"operands": [
+								{
+									"@type": "koral:term",
+									"foundry": "opennlp",
+									"key": "AdjType",
+									"layer": "m",
+									"match": "match:eq",
+									"value": "Pdt"
+								},
+								{
+									"@type": "koral:term",
+									"foundry": "opennlp",
+									"key": "PronType",
+									"layer": "m",
+									"match": "match:ne",
+									"value": "Neg"
+								}
+							],
+							"relation": "relation:or"
+						}
+					],
+					"relation": "relation:and"
+				}
+			}`,
+			expected: &ast.Token{
+				Wrap: &ast.TermGroup{
+					Operands: []ast.Node{
+						&ast.Term{
+							Foundry: "opennlp",
+							Key:     "DET",
+							Layer:   "p",
+							Match:   ast.MatchEqual,
+						},
+						&ast.TermGroup{
+							Operands: []ast.Node{
+								&ast.Term{
+									Foundry: "opennlp",
+									Key:     "AdjType",
+									Layer:   "m",
+									Match:   ast.MatchEqual,
+									Value:   "Pdt",
+								},
+								&ast.Term{
+									Foundry: "opennlp",
+									Key:     "PronType",
+									Layer:   "m",
+									Match:   ast.MatchNotEqual,
+									Value:   "Neg",
+								},
+							},
+							Relation: ast.OrRelation,
+						},
+					},
+					Relation: ast.AndRelation,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:    "Invalid JSON",
+			input:   `{"invalid": json`,
+			wantErr: true,
+		},
+		{
+			name:    "Empty JSON",
+			input:   `{}`,
+			wantErr: true,
+		},
+		{
+			name: "Invalid node type",
+			input: `{
+				"@type": "koral:unknown",
+				"key": "value"
+			}`,
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result, err := ParseJSON([]byte(tt.input))
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			require.NoError(t, err)
+			assert.Equal(t, tt.expected, result)
+		})
+	}
+}
+
+func TestSerializeToJSON(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    ast.Node
+		expected string
+		wantErr  bool
+	}{
+		{
+			name: "Serialize simple term",
+			input: &ast.Term{
+				Foundry: "opennlp",
+				Key:     "DET",
+				Layer:   "p",
+				Match:   ast.MatchEqual,
+			},
+			expected: `{
+  "@type": "koral:term",
+  "foundry": "opennlp",
+  "key": "DET",
+  "layer": "p",
+  "match": "match:eq"
+}`,
+			wantErr: false,
+		},
+		{
+			name: "Serialize term group",
+			input: &ast.TermGroup{
+				Operands: []ast.Node{
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "DET",
+						Layer:   "p",
+						Match:   ast.MatchEqual,
+					},
+					&ast.Term{
+						Foundry: "opennlp",
+						Key:     "AdjType",
+						Layer:   "m",
+						Match:   ast.MatchEqual,
+						Value:   "Pdt",
+					},
+				},
+				Relation: ast.AndRelation,
+			},
+			expected: `{
+  "@type": "koral:termGroup",
+  "operands": [
+    {
+      "@type": "koral:term",
+      "foundry": "opennlp",
+      "key": "DET",
+      "layer": "p",
+      "match": "match:eq"
+    },
+    {
+      "@type": "koral:term",
+      "foundry": "opennlp",
+      "key": "AdjType",
+      "layer": "m",
+      "match": "match:eq",
+      "value": "Pdt"
+    }
+  ],
+  "relation": "relation:and"
+}`,
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result, err := SerializeToJSON(tt.input)
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			require.NoError(t, err)
+			// Compare JSON objects instead of raw strings to avoid whitespace issues
+			var expected, actual interface{}
+			err = json.Unmarshal([]byte(tt.expected), &expected)
+			require.NoError(t, err)
+			err = json.Unmarshal(result, &actual)
+			require.NoError(t, err)
+			assert.Equal(t, expected, actual)
+		})
+	}
+}
+
+func TestRoundTrip(t *testing.T) {
+	// Test that parsing and then serializing produces equivalent JSON
+	input := `{
+		"@type": "koral:token",
+		"wrap": {
+			"@type": "koral:termGroup",
+			"operands": [
+				{
+					"@type": "koral:term",
+					"foundry": "opennlp",
+					"key": "DET",
+					"layer": "p",
+					"match": "match:eq"
+				},
+				{
+					"@type": "koral:term",
+					"foundry": "opennlp",
+					"key": "AdjType",
+					"layer": "m",
+					"match": "match:eq",
+					"value": "Pdt"
+				}
+			],
+			"relation": "relation:and"
+		}
+	}`
+
+	// Parse JSON to AST
+	node, err := ParseJSON([]byte(input))
+	require.NoError(t, err)
+
+	// Serialize AST back to JSON
+	output, err := SerializeToJSON(node)
+	require.NoError(t, err)
+
+	// Compare JSON objects
+	var expected, actual interface{}
+	err = json.Unmarshal([]byte(input), &expected)
+	require.NoError(t, err)
+	err = json.Unmarshal(output, &actual)
+	require.NoError(t, err)
+	assert.Equal(t, expected, actual)
+}
diff --git a/termmapper.go b/termmapper.go
deleted file mode 100644
index 791c112..0000000
--- a/termmapper.go
+++ /dev/null
@@ -1,595 +0,0 @@
-package termmapper
-
-/*
-$(	=>	PUNCT	PunctType=Brck	``, '', *RRB*, *LRB*, -
-$,	=>	PUNCT	PunctType=Comm	,
-$.	=>	PUNCT	PunctType=Peri	., :, ?, ;, !
-ADJA	=>	ADJ	_	neuen, neue, deutschen, ersten, anderen
-ADJD	=>	ADJ	Variant=Short	gut, rund, knapp, deutlich, möglich
-ADV	=>	ADV	_	auch, nur, noch, so, aber
-APPO	=>	ADP	AdpType=Post	zufolge, nach, gegenüber, wegen, über
-APPR	=>	ADP	AdpType=Prep	in, von, mit, für, auf
-APPRART	=>	ADP	AdpType=Prep|PronType=Art	im, am, zum, zur, vom
-APZR	=>	ADP	AdpType=Circ	an, hinaus, aus, her, heraus
-ART	=>	DET	PronType=Art	der, die, den, des, das
-CARD	=>	NUM	NumType=Card	000, zwei, drei, vier, fünf
-FM	=>	X	Foreign=Yes	New, of, de, Times, the
-ITJ	=>	INTJ	_	naja, Ach, äh, Na, piep
-KOKOM	=>	CCONJ	ConjType=Comp	als, wie, denn, wir
-KON	=>	CCONJ	_	und, oder, sondern, sowie, aber
-KOUI	=>	SCONJ	_	um, ohne, statt, anstatt, Ums
-KOUS	=>	SCONJ	_	daß, wenn, weil, ob, als
-NE	=>	PROPN	_	SPD, Deutschland, USA, dpa, Bonn
-NN	=>	NOUN	_	Prozent, Mark, Millionen, November, Jahren
-PAV	=>	ADV	PronType=Dem
-PDAT	=>	DET	PronType=Dem	dieser, diese, diesem, dieses, diesen
-PDS	=>	PRON	PronType=Dem	das, dies, die, diese, der
-PIAT	=>	DET	PronType=Ind,Neg,Tot	keine, mehr, alle, kein, beiden
-PIDAT	=>	DET	AdjType=Pdt|PronType=Ind,Neg,Tot
-PIS	=>	PRON	PronType=Ind,Neg,Tot	man, allem, nichts, alles, mehr
-
-
-PIS => PRON & PronType=[Ind|Neg|Tot]
-
-PPER	=>	PRON	PronType=Prs	es, sie, er, wir, ich
-PPOSAT	=>	DET	Poss=Yes|PronType=Prs	ihre, seine, seiner, ihrer, ihren
-PPOSS	=>	PRON	Poss=Yes|PronType=Prs	ihren, Seinen, seinem, unsrigen, meiner
-PRELAT	=>	DET	PronType=Rel	deren, dessen, die
-PRELS	=>	PRON	PronType=Rel	die, der, das, dem, denen
-PRF	=>	PRON	PronType=Prs|Reflex=Yes	sich, uns, mich, mir, dich
-PTKA	=>	PART	_	zu, am, allzu, Um
-PTKANT	=>	PART	PartType=Res	nein, ja, bitte, Gewiß, Also
-PTKNEG	=>	PART	Polarity=Neg	nicht
-PTKVZ	=>	ADP	PartType=Vbp	an, aus, ab, vor, auf
-PTKZU	=>	PART	PartType=Inf	zu, zur, zum
-PWAT	=>	DET	PronType=Int	welche, welchen, welcher, wie, welchem
-PWAV	=>	ADV	PronType=Int	wie, wo, warum, wobei, wonach
-PWS	=>	PRON	PronType=Int	was, wer, wem, wen, welches
-TRUNC	=>	X	Hyph=Yes	Staats-, Industrie-, Finanz-, Öl-, Lohn-
-VAFIN	=>	AUX	Mood=Ind|VerbForm=Fin	ist, hat, wird, sind, sei
-VAIMP	=>	AUX	Mood=Imp|VerbForm=Fin	Seid, werde, Sei
-VAINF	=>	AUX	VerbForm=Inf	werden, sein, haben, worden, Dabeisein
-VAPP	=>	AUX	Aspect=Perf|VerbForm=Part	worden, gewesen, geworden, gehabt, werden
-VMFIN	=>	VERB	Mood=Ind|VerbForm=Fin|VerbType=Mod	kann, soll, will, muß, sollen
-VMINF	=>	VERB	VerbForm=Inf|VerbType=Mod	können, müssen, wollen, dürfen, sollen
-VMPP	=>	VERB	Aspect=Perf|VerbForm=Part|VerbType=Mod	gewollt
-VVFIN	=>	VERB	Mood=Ind|VerbForm=Fin	sagte, gibt, geht, steht, kommt
-VVIMP	=>	VERB	Mood=Imp|VerbForm=Fin	siehe, sprich, schauen, Sagen, gestehe
-VVINF	=>	VERB	VerbForm=Inf	machen, lassen, bleiben, geben, bringen
-VVIZU	=>	VERB	VerbForm=Inf	einzusetzen, durchzusetzen, aufzunehmen, abzubauen, umzusetzen
-VVPP	=>	VERB	Aspect=Perf|VerbForm=Part	gemacht, getötet, gefordert, gegeben, gestellt
-XY	=>	X	_	dpa, ap, afp, rtr, wb
-*/
-
-import (
-	"strconv"
-	"strings"
-
-	"github.com/rs/zerolog/log"
-	"github.com/tidwall/gjson"
-	"github.com/tidwall/sjson"
-)
-
-/*
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-	"strings"
-)
-
-var mapping = map[string]string{
-	"$(":"PUNCT",
-}
-
-// Recursive function to turn the UPos query into a STTS query
-func koralRewriteUpos2Stts(koralquery interface{}) interface{} {
-	switch v := koralquery.(type) {
-	case map[string]interface{}:
-		// Check for '@type' key and act accordingly
-		if typ, ok := v["@type"].(string); ok {
-			switch typ {
-			case "koral:term":
-
-        // Modify the key to use STTS
-// This may require to turn object into a koral:token with terms like:
-
-
-				if key, ok := v["key"].(string); ok {
-					v["key"] = "hallo-" + key
-				}
-			case "operation":
-				// Handle the 'operators' key by recursively modifying each operator
-				if operators, ok := v["operators"].([]interface{}); ok {
-					for i, operator := range operators {
-						operators[i] = modifyJSON(operator)
-					}
-					v["operators"] = operators
-				}
-			}
-		}
-		// Recursively modify any nested maps
-		for k, val := range v {
-			v[k] = modifyJSON(val)
-		}
-		return v
-	case []interface{}:
-		// Recursively modify elements of arrays
-		for i, item := range v {
-			v[i] = modifyJSON(item)
-		}
-		return v
-	}
-	return koralquery
-}
-
-func main() {
-	// Sample JSON input string
-	jsonStr := `{
-		"@type": "operation",
-		"operators": [
-			{
-				"@type": "term",
-				"key": "example1"
-			},
-			{
-				"@type": "term",
-				"key": "example2"
-			},
-			{
-				"@type": "operation",
-				"operators": [
-					{
-						"@type": "term",
-						"key": "nested"
-					}
-				]
-			}
-		]
-	}`
-
-	// Parse the JSON string into a generic interface{}
-	var data interface{}
-	err := json.Unmarshal([]byte(jsonStr), &data)
-	if err != nil {
-		log.Fatal("Error unmarshaling JSON:", err)
-	}
-
-	// Modify the JSON structure recursively
-	modifiedData := modifyJSON(data)
-
-	// Marshal the modified data back into a JSON string
-	modifiedJSON, err := json.MarshalIndent(modifiedData, "", "  ")
-	if err != nil {
-		log.Fatal("Error marshaling JSON:", err)
-	}
-
-	// Output the modified JSON string
-	fmt.Println(string(modifiedJSON))
-}
-
-
-
-func turnupostostts(json string, targetFoundry string, targetLayer string) {
-	if targetLayer == "" {
-		targetLayer = "p"
-	}
-
-  ldType := "@type"
-
-  if ldType == "koral:span" {
-    next
-  }
-  if ldType == "koral:term"  {
-      if foundry == if layer === key -> rewrite
-  }
-
-	// Iterate through the query and whenever a term is requested without a foundry, and without a layser or layer p,
-  // change the key following the mapping
-
-
-}
-
-func addupostooutput(json string, reffoundry string, foundry string) {
-	// https://universaldependencies.org/tagset-conversion/de-stts-uposf.html
-	// Iterate through all matches and add to all xml snippets a line of foundry
-
-}
-
-*/
-
-type Term struct {
-	Foundry string
-	Layer   string
-	Key     string
-}
-
-func Map2(json []byte) string {
-	/*
-		result := gjson.GetBytes(json, "query")
-		var raw []byte
-		if result.Index > 0 {
-			raw = json[result.Index:result.Index+len(result.Raw)]
-		} else {
-			raw = []byte(result.Raw)
-		}
-
-		if result.IsObject() {
-			koralType := gjson.GetBytes(raw, "@type").String()
-			switch koralType {
-				case "koral:term":
-
-			}
-		}
-	*/
-
-	koralObj := gjson.ParseBytes(json)
-
-	switch koralObj.Get("@type").String() {
-	case "koral:term":
-		{
-			if koralObj.Get("value").String() == "KOKOM" {
-				// TODO: Turn this in a token, if it isn't already!
-				newJson, _ := sjson.Set(string(json), "value", "CCONJ")
-				return newJson
-			}
-		}
-
-	case "koral:operation":
-		{
-
-		}
-
-	}
-	/*
-
-		var raw []byte
-		if result.Index > 0 {
-			raw = json[result.Index:result.Index+len(result.Raw)]
-		} else {
-			raw = []byte(result.Raw)
-		}
-	*/
-	return "jj"
-}
-
-// token writes a token to the string builder
-func token(strBuilder *strings.Builder, terms []Term, positive bool) {
-	strBuilder.WriteString(`{"@type":"koral:token","wrap":`)
-	if len(terms) > 1 {
-		termGroup(strBuilder, terms, true, positive)
-	} else {
-		term(strBuilder, terms[0], positive)
-	}
-	strBuilder.WriteString(`}`)
-}
-
-// termGroup writes a termGroup to the string builder
-func termGroup(strBuilder *strings.Builder, terms []Term, operationAnd bool, positive bool) {
-	strBuilder.WriteString(`{"@type":"koral:termGroup",`)
-
-	if operationAnd {
-		strBuilder.WriteString(`"relation":"relation:and","operation":"operation:and",`)
-	} else {
-		strBuilder.WriteString(`"relation":"relation:or","operation":"operation:or",`)
-	}
-
-	strBuilder.WriteString(`"operands":[`)
-	for i, t := range terms {
-		term(strBuilder, t, positive)
-		if i < len(terms)-1 {
-			strBuilder.WriteString(",")
-		}
-	}
-	strBuilder.WriteString(`]}`)
-}
-
-// term writes a term to the string builder
-func term(strBuilder *strings.Builder, term Term, positive bool) {
-	strBuilder.WriteString(`{"@type":"koral:term","match":"match:`)
-	if positive {
-		strBuilder.WriteString("eq")
-	} else {
-		strBuilder.WriteString("ne")
-	}
-	strBuilder.WriteString(`","foundry":"`)
-	strBuilder.WriteString(term.Foundry)
-	strBuilder.WriteString(`","layer":"`)
-	strBuilder.WriteString(term.Layer)
-	strBuilder.WriteString(`","key":"`)
-	strBuilder.WriteString(term.Key)
-	strBuilder.WriteString(`"}`)
-}
-
-func flatten() {
-
-	// if a termGroup isan operand in a termGroup with the same relation/operation:
-	// flatten the termGroup into the parent termGroup
-
-	// if a termGroup has only a single term, remove the group
-}
-
-// replaceWrappedTerm replaces the wrapped term with the new term group
-func replaceWrappedTerms(jsonString string, terms []Term) string {
-	var err error
-
-	// Replace with a single term
-	if len(terms) == 1 {
-		jsonString, err = sjson.Set(jsonString, "foundry", terms[0].Foundry)
-		if err != nil {
-			log.Error().Err(err).Msg("Error setting foundry")
-		}
-		jsonString, err = sjson.Set(jsonString, "layer", terms[0].Layer)
-		if err != nil {
-			log.Error().Err(err).Msg("Error setting layer")
-		}
-		jsonString, err = sjson.Set(jsonString, "key", terms[0].Key)
-		if err != nil {
-			log.Error().Err(err).Msg("Error setting key")
-		}
-
-		return jsonString
-	}
-
-	matchop := gjson.Get(jsonString, "match").String()
-
-	/*
-		foundry := gjson.Get(jsonString, "foundry").String()
-		layer := gjson.Get(jsonString, "layer").String()
-		key := gjson.Get(jsonString, "key").String()
-		term := Term{foundry, layer, key}
-
-
-		terms = append(terms, term)
-	*/
-
-	var strBuilder strings.Builder
-	if matchop == "match:ne" {
-		// ! Make or-Group with nes
-		termGroup(&strBuilder, terms, false, false)
-	} else {
-		termGroup(&strBuilder, terms, true, true)
-	}
-
-	return strBuilder.String()
-
-}
-
-func replaceGroupedTerm(jsonString string, op []int, foundry string, layer string, key string) string {
-	var err error
-
-	strInt := "operands." + strconv.Itoa(op[0]) + "."
-	jsonString, err = sjson.Set(jsonString, strInt+"foundry", foundry)
-	if err != nil {
-		log.Error().Err(err).Msg("Error setting foundry")
-	}
-	jsonString, err = sjson.Set(jsonString, strInt+"layer", layer)
-	if err != nil {
-		log.Error().Err(err).Msg("Error setting layer")
-	}
-	jsonString, err = sjson.Set(jsonString, strInt+"key", key)
-	if err != nil {
-		log.Error().Err(err).Msg("Error setting key")
-	}
-
-	if len(op) > 1 {
-		for i := 1; i < len(op); i++ {
-			jsonString, err = sjson.Delete(jsonString, "operands."+strconv.Itoa(op[i]))
-			if err != nil {
-				log.Error().Err(err).Msg("Error deleting operand")
-			}
-		}
-	}
-
-	return jsonString
-}
-
-func replaceGroupedTerms(jsonString string, op []int, terms []Term) string {
-	var err error
-
-	positive := true
-	operationAnd := true
-
-	operation := gjson.Get(jsonString, "operation")
-	if operation.String() == "operation:or" {
-		operationAnd = false
-	}
-
-	// TODO:
-	// matchop := gjson.Get(jsonString, strInt).String()
-
-	if len(op) == 1 {
-		strInt := "operands." + strconv.Itoa(op[0]) + ".match"
-
-		matchop := gjson.Get(jsonString, strInt).String()
-
-		if matchop == "match:ne" {
-			positive = false
-		}
-
-		// Delete the first term
-		jsonString, err = sjson.Delete(jsonString, strInt)
-
-		if err != nil {
-			log.Error().Err(err).Msg("Error deleting match")
-		}
-	}
-
-	for i := 0; i < len(op); i++ {
-		jsonString, err = sjson.Delete(jsonString, "operands."+strconv.Itoa(op[i]))
-
-		if err != nil {
-			log.Error().Err(err).Msg("Error deleting operand")
-		}
-	}
-
-	// TODO:
-	// Check if the group has only a single operand!
-
-	// TODO:
-	// All terms in the group require the same match!
-	// It's not possible to deal with !a & b
-	/*
-		jsonString, err = sjson.Set(jsonString, strInt+"foundry", foundry)
-		if err != nil {
-			log.Error().Err(err).Msg("Error setting foundry")
-		}
-		jsonString, err = sjson.Set(jsonString, strInt+"layer", layer)
-		if err != nil {
-			log.Error().Err(err).Msg("Error setting layer")
-		}
-		jsonString, err = sjson.Set(jsonString, strInt+"key", key)
-		if err != nil {
-			log.Error().Err(err).Msg("Error setting key")
-		}
-
-		if len(op) > 1 {
-			for i := 1; i < len(op); i++ {
-				jsonString, err = sjson.Delete(jsonString, "operands."+strconv.Itoa(op[i]))
-				if err != nil {
-					log.Error().Err(err).Msg("Error deleting operand")
-				}
-			}
-		}
-	*/
-
-	var strBuilder strings.Builder
-	// Embed a new termGroup
-	if !operationAnd {
-		termGroup(&strBuilder, terms, true, false)
-		jsonString, err = sjson.SetRaw(jsonString, "operands.-1", strBuilder.String())
-		if err != nil {
-			log.Error().Err(err).Msg("Error adding termGroup")
-		}
-		strBuilder.Reset()
-	} else if !positive {
-		termGroup(&strBuilder, terms, false, false)
-		jsonString, err = sjson.SetRaw(jsonString, "operands.-1", strBuilder.String())
-		if err != nil {
-			log.Error().Err(err).Msg("Error adding termGroup")
-		}
-		strBuilder.Reset()
-	} else {
-		for i := 0; i < len(terms); i++ {
-			term(&strBuilder, terms[i], positive)
-			jsonString, err = sjson.SetRaw(jsonString, "operands.-1", strBuilder.String())
-
-			if err != nil {
-				log.Error().Err(err).Msg("Error adding term")
-			}
-			strBuilder.Reset()
-		}
-	}
-
-	return jsonString
-}
-
-/*
-func replaceTermWithToken(jsonString string) string {
-	// Replace the term with the token
-	replacedString, err := sjson.Set(jsonString, "wrap.operands.0", token())
-	if err != nil {
-		return jsonString // Return the original string in case of an error
-	}
-	return replacedString
-
-// case1: 1 -> 1 the term is an operand in a termGroup with the same relation/operation
-// case2: 1 -> 1 the term is wrapped
-// case3: 1 -> 1 the term is an operand in a termGroup with a different relation/operation
-// case4: n -> 1 the term is an operand in a termGroup with the same relation/operation
-// case5: n -> 1 the term is wrapped
-// case6: n -> 1 the term is an operand in a termGroup with a different relation/operation
-// case7: 1 -> n the term is an operand in a termGroup with the same relation/operation
-// case8: 1 -> n the term is wrapped
-// case9: 1 -> n the term is an operand in a termGroup with a different relation/operation
-	}
-*/
-
-func Map(jsonStr string) string {
-
-	obj := gjson.Get(jsonStr, "query")
-
-	// value := gjson.Get(json, "name.last")
-
-	/*
-
-	   	// Modify the JSON structure recursively
-	   	modifiedData := modifyJSON(ast.NewAny(data))
-
-	   // Marshal the modified data back into a JSON string
-	   	modifiedJSON, err := sonic.MarshalString(modifiedData)
-
-	   	// Parse the JSON string into a generic interface{}
-	   	var data interface{}
-
-	   	err := sonic.UnmarshalString(jsonStr, data)
-
-	   	if err != nil {
-	   		log.Fatal("Error unmarshaling JSON:", err)
-	   		return ""
-	   	}
-
-
-
-	   	if err != nil {
-	   		log.Fatal("Error marshaling JSON:", err)
-	   	}
-	*/
-	// Output the modified JSON string
-	return obj.String() //modifyJSON(obj)
-}
-
-// Recursive function to modify JSON using Sonic library
-//func modifyJSON(data gjson.Result) string {
-
-// Check if data is a map
-// if data.IsObject() {
-/*
-	dataMap := data.Map()
-
-	koralType := dataMap["@type"].String()
-
-	// Look for @type key
-
-	switch koralType {
-	case "koral:term":
-		// Modify the key by adding 'hallo-' prefix
-
-		// sjson.SetRaw(data.String())
-		sjson.Set(data.Path(data.Bytes()), "key", "hallo-"+dataMap["key"].String())
-
-		dataMap["key"] = "hallo-" + dataMap["key"].String()
-		/*
-			if key, found := data.GetString("key"); found {
-				data.Set("key", "hallo-"+key)
-			}
-*/
-/*
-	case "koral:operation":
-			// Handle the 'operators' key by recursively modifying each operator
-			if operators, found := data.GetArray("operators"); found {
-				for i := range operators {
-					operators[i] = modifyJSON(operators[i])
-				}
-				data.Set("operators", operators)
-			}
-		}*/
-/*
-	// Recursively modify any nested objects
-	data.ForEach(func(k string, v sonic.Any) {
-		data.Set(k, modifyJSON(v))
-	})
-*/
-//}
-// Handle arrays by modifying elements recursively
-/*
-	if data.IsArray() {
-		for i := range data.GetArray() {
-			data.Set(i, modifyJSON(data.GetArray()[i]))
-		}
-	}
-*/
-/*
-	return data
-}
-*/
diff --git a/termmapper_test.go b/termmapper_test.go
deleted file mode 100644
index 870e45e..0000000
--- a/termmapper_test.go
+++ /dev/null
@@ -1,200 +0,0 @@
-package termmapper
-
-import (
-	"strings"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-// KoralPipe-TermMapping
-
-func TestTokenBuilder(t *testing.T) {
-
-	assert := assert.New(t)
-
-	var strBuilder strings.Builder
-	term(&strBuilder, Term{"myfoundry", "mylayer", "mykey1"}, true)
-	assert.Equal(strBuilder.String(), `{"@type":"koral:term","match":"match:eq","foundry":"myfoundry","layer":"mylayer","key":"mykey1"}`)
-	strBuilder.Reset()
-
-	token(&strBuilder, []Term{{"myfoundry", "mylayer", "mykey1"}, {"myfoundry", "mylayer", "mykey2"}}, true)
-	assert.Equal(strBuilder.String(), "{\"@type\":\"koral:token\",\"wrap\":{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey1\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}}")
-	strBuilder.Reset()
-
-	token(&strBuilder, []Term{{"myfoundry", "mylayer", "mykey2"}}, true)
-	assert.Equal(strBuilder.String(), "{\"@type\":\"koral:token\",\"wrap\":{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}}")
-	strBuilder.Reset()
-}
-
-/*
-		jsonStr := `{
-		"query": {
-			"@type": "koral:operation",
-			"operators": [
-				{
-					"@type": "koral:term",
-					"key": "example1"
-				},
-				{
-					"@type": "koral:term",
-					"key": "example2"
-				},
-				{
-					"@type": "koral:operation",
-					"operators": [
-						{
-							"@type": "koral:term",
-							"key": "nested"
-						}
-					]
-				}
-			]
-		}
-	}`
-*/
-
-func TestTermReplacement(t *testing.T) {
-
-	assert := assert.New(t)
-
-	// case1: 1 -> 1 the term is wrapped with eq
-	// case1: 1 -> 1 the term is wrapped with ne
-	// [ADV] -> [ADV]
-	testStr := replaceWrappedTerms(
-		"{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"}",
-		[]Term{{"myfoundry2",
-			"mylayer2",
-			"mykey2",
-		}},
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry2\",\"layer\":\"mylayer2\",\"key\":\"mykey2\"}")
-
-	testStr = replaceWrappedTerms(
-		"{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"}",
-		[]Term{{"myfoundry2",
-			"mylayer2",
-			"mykey2",
-		}},
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry2\",\"layer\":\"mylayer2\",\"key\":\"mykey2\"}")
-
-	// case2: 1 -> 1 the term is an operand in a termGroup with the same relation/operation
-	// [ADV & ...] -> [ADV]
-	// case3: 1 -> 1 the term is an operand in a termGroup with a different relation/operation
-	// [ADV | ...] -> [ADV]
-	testStr = replaceGroupedTerm(
-		"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}",
-		[]int{0},
-		"myfoundryX",
-		"mylayerX",
-		"mykeyX",
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundryX\",\"layer\":\"mylayerX\",\"key\":\"mykeyX\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}")
-
-	testStr = replaceGroupedTerm(
-		"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}",
-		[]int{0},
-		"myfoundryX",
-		"mylayerX",
-		"mykeyX",
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundryX\",\"layer\":\"mylayerX\",\"key\":\"mykeyX\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}")
-
-	// case4: n -> 1 the term is an operand in a termGroup with the same relation/operation
-	// [PRON & Poss=yes & PronType=Prs] -> [PPOSAT]
-	testStr = replaceGroupedTerm(
-		"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}",
-		[]int{0, 1},
-		"myfoundryX",
-		"mylayerX",
-		"mykeyX",
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundryX\",\"layer\":\"mylayerX\",\"key\":\"mykeyX\"}]}")
-
-	// case5: 1 -> n the term is wrapped
-	// [PPOSAT] -> [PRON & Poss=yes & PronType=Prs]
-	testStr = replaceWrappedTerms(
-		"{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"}",
-		[]Term{{
-			"myfoundry1",
-			"mylayer1",
-			"mykey1",
-		}, {
-			"myfoundry2",
-			"mylayer2",
-			"mykey2",
-		}},
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry1\",\"layer\":\"mylayer1\",\"key\":\"mykey1\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry2\",\"layer\":\"mylayer2\",\"key\":\"mykey2\"}]}")
-
-	// [!PPOSAT] -> [!PRON | !Poss=yes | !PronType=Prs]
-	testStr = replaceWrappedTerms(
-		"{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey\"}",
-		[]Term{{
-			"myfoundry1",
-			"mylayer1",
-			"mykey1",
-		}, {
-			"myfoundry2",
-			"mylayer2",
-			"mykey2",
-		}},
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:termGroup\",\"relation\":\"relation:or\",\"operation\":\"operation:or\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry1\",\"layer\":\"mylayer1\",\"key\":\"mykey1\"},{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry2\",\"layer\":\"mylayer2\",\"key\":\"mykey2\"}]}")
-
-	// case6: 1 -> n the term is an operand in a termGroup with the same relation/operation
-	// [PPOSAT & ...] -> [PRON & Poss=yes & PronType=Prs]
-	testStr = replaceGroupedTerms(
-		"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey1\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}]}",
-		[]int{0},
-		[]Term{{
-			"myfoundry3",
-			"mylayer3",
-			"mykey3",
-		}, {
-			"myfoundry4",
-			"mylayer4",
-			"mykey4",
-		}},
-	)
-	assert.Equal(testStr, "{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":[{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry3\",\"layer\":\"mylayer3\",\"key\":\"mykey3\"},{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry4\",\"layer\":\"mylayer4\",\"key\":\"mykey4\"}]}")
-
-	// case7: 1 -> n the term is an operand in a termGroup with a different relation/operation
-	testStr = replaceGroupedTerms(
-
-		"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":["+
-			"{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey1\"},"+
-			"{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"}"+
-			"]}",
-		[]int{0},
-		[]Term{{
-			"myfoundry3",
-			"mylayer3",
-			"mykey3",
-		}, {
-			"myfoundry4",
-			"mylayer4",
-			"mykey4",
-		}},
-	)
-
-	// TODO: Add a termGroup with reversed signs
-	assert.Equal(testStr,
-
-		"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:and\",\"operation\":\"operation:and\",\"operands\":["+
-
-			"{\"@type\":\"koral:term\",\"match\":\"match:eq\",\"foundry\":\"myfoundry\",\"layer\":\"mylayer\",\"key\":\"mykey2\"},"+
-
-			"{\"@type\":\"koral:termGroup\",\"relation\":\"relation:or\",\"operation\":\"operation:or\",\"operands\":["+
-
-			"{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry3\",\"layer\":\"mylayer3\",\"key\":\"mykey3\"},"+
-			"{\"@type\":\"koral:term\",\"match\":\"match:ne\",\"foundry\":\"myfoundry4\",\"layer\":\"mylayer4\",\"key\":\"mykey4\"}"+
-			"]}"+
-
-			"]"+
-			"}")
-	// case8: n -> n the term is an operand in a termGroup with the same relation/operation
-	// case9: n -> n the term is an operand in a termGroup with a different relation/operation
-
-}