blob: f54052528a14953a075307eaf0abe65a7a554c39 [file] [log] [blame]
Akron32d53de2025-05-22 13:45:32 +02001package mapper
2
3import (
4 "encoding/json"
5 "fmt"
6
7 "github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
8 "github.com/KorAP/KoralPipe-TermMapper2/pkg/config"
9 "github.com/KorAP/KoralPipe-TermMapper2/pkg/matcher"
10 "github.com/KorAP/KoralPipe-TermMapper2/pkg/parser"
11)
12
13// Direction represents the mapping direction (A to B or B to A)
14type Direction string
15
16const (
17 AtoB Direction = "atob"
18 BtoA Direction = "btoa"
19)
20
21// Mapper handles the application of mapping rules to JSON objects
22type Mapper struct {
23 mappingLists map[string]*config.MappingList
24 parsedRules map[string][]*parser.MappingResult
25}
26
27// NewMapper creates a new Mapper instance
28func NewMapper(configFiles ...string) (*Mapper, error) {
29 m := &Mapper{
30 mappingLists: make(map[string]*config.MappingList),
31 parsedRules: make(map[string][]*parser.MappingResult),
32 }
33
34 // Load and parse all config files
35 for _, file := range configFiles {
36 cfg, err := config.LoadConfig(file)
37 if err != nil {
38 return nil, fmt.Errorf("failed to load config from %s: %w", file, err)
39 }
40
41 // Store mapping lists by ID
42 for _, list := range cfg.Lists {
43 if _, exists := m.mappingLists[list.ID]; exists {
44 return nil, fmt.Errorf("duplicate mapping list ID found: %s", list.ID)
45 }
46
47 // Create a copy of the list to store
48 listCopy := list
49 m.mappingLists[list.ID] = &listCopy
50
51 // Parse the rules immediately
52 parsedRules, err := list.ParseMappings()
53 if err != nil {
54 return nil, fmt.Errorf("failed to parse mappings for list %s: %w", list.ID, err)
55 }
56 m.parsedRules[list.ID] = parsedRules
57 }
58 }
59
60 return m, nil
61}
62
63// MappingOptions contains the options for applying mappings
64type MappingOptions struct {
65 FoundryA string
66 LayerA string
67 FoundryB string
68 LayerB string
69 Direction Direction
70}
71
72// ApplyMappings applies the specified mapping rules to a JSON object
73func (m *Mapper) ApplyMappings(mappingID string, opts MappingOptions, jsonData interface{}) (interface{}, error) {
74 // Validate mapping ID
75 if _, exists := m.mappingLists[mappingID]; !exists {
76 return nil, fmt.Errorf("mapping list with ID %s not found", mappingID)
77 }
78
79 // Validate direction
80 if opts.Direction != AtoB && opts.Direction != BtoA {
81 return nil, fmt.Errorf("invalid direction: %s", opts.Direction)
82 }
83
84 // Get the parsed rules
85 rules := m.parsedRules[mappingID]
86
87 // Convert input JSON to AST
88 jsonBytes, err := json.Marshal(jsonData)
89 if err != nil {
90 return nil, fmt.Errorf("failed to marshal input JSON: %w", err)
91 }
92
93 node, err := parser.ParseJSON(jsonBytes)
94 if err != nil {
95 return nil, fmt.Errorf("failed to parse JSON into AST: %w", err)
96 }
97
98 // Extract the inner node if it's a token
99 if token, ok := node.(*ast.Token); ok {
100 node = token.Wrap
101 }
102
103 // Apply each rule to the AST
104 for _, rule := range rules {
105 // Create pattern and replacement based on direction
106 var pattern, replacement ast.Node
107 if opts.Direction == AtoB {
108 pattern = rule.Upper
109 replacement = rule.Lower
110 } else {
111 pattern = rule.Lower
112 replacement = rule.Upper
113 }
114
115 // Extract the inner nodes from the pattern and replacement tokens
116 if token, ok := pattern.(*ast.Token); ok {
117 pattern = token.Wrap
118 }
119 if token, ok := replacement.(*ast.Token); ok {
120 replacement = token.Wrap
121 }
122
123 // Apply foundry and layer overrides
124 if opts.Direction == AtoB {
125 applyFoundryAndLayerOverrides(pattern, opts.FoundryA, opts.LayerA)
126 applyFoundryAndLayerOverrides(replacement, opts.FoundryB, opts.LayerB)
127 } else {
128 applyFoundryAndLayerOverrides(pattern, opts.FoundryB, opts.LayerB)
129 applyFoundryAndLayerOverrides(replacement, opts.FoundryA, opts.LayerA)
130 }
131
132 // Create matcher and apply replacement
133 m := matcher.NewMatcher(ast.Pattern{Root: pattern}, ast.Replacement{Root: replacement})
134 node = m.Replace(node)
135 }
136
137 // Wrap the result in a token
138 result := &ast.Token{Wrap: node}
139
140 // Convert AST back to JSON
141 resultBytes, err := parser.SerializeToJSON(result)
142 if err != nil {
143 return nil, fmt.Errorf("failed to serialize AST to JSON: %w", err)
144 }
145
146 // Parse the JSON string back into an interface{}
147 var resultData interface{}
148 if err := json.Unmarshal(resultBytes, &resultData); err != nil {
149 return nil, fmt.Errorf("failed to parse result JSON: %w", err)
150 }
151
152 return resultData, nil
153}
154
155// applyFoundryAndLayerOverrides recursively applies foundry and layer overrides to terms
156func applyFoundryAndLayerOverrides(node ast.Node, foundry, layer string) {
157 if node == nil {
158 return
159 }
160
161 switch n := node.(type) {
162 case *ast.Term:
163 if foundry != "" {
164 n.Foundry = foundry
165 }
166 if layer != "" {
167 n.Layer = layer
168 }
169 case *ast.TermGroup:
170 for _, op := range n.Operands {
171 applyFoundryAndLayerOverrides(op, foundry, layer)
172 }
173 case *ast.Token:
174 if n.Wrap != nil {
175 applyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
176 }
177 case *ast.CatchallNode:
178 if n.Wrap != nil {
179 applyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
180 }
181 for _, op := range n.Operands {
182 applyFoundryAndLayerOverrides(op, foundry, layer)
183 }
184 }
185}