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
-
-}