blob: 0bc7d7e89ec6fd77855c0bd22ad820d274ad5009 [file] [log] [blame]
Akron4de47a92025-06-27 11:58:11 +02001package mapper // ApplyQueryMappings applies the specified mapping rules to a JSON object
2
3import (
4 "encoding/json"
5 "fmt"
6
7 "github.com/KorAP/KoralPipe-TermMapper/ast"
8 "github.com/KorAP/KoralPipe-TermMapper/matcher"
9 "github.com/KorAP/KoralPipe-TermMapper/parser"
10)
11
12// ApplyQueryMappings applies the specified mapping rules to a JSON object
13func (m *Mapper) ApplyQueryMappings(mappingID string, opts MappingOptions, jsonData any) (any, error) {
14 // Validate mapping ID
15 if _, exists := m.mappingLists[mappingID]; !exists {
16 return nil, fmt.Errorf("mapping list with ID %s not found", mappingID)
17 }
18
19 // Get the parsed rules
20 rules := m.parsedRules[mappingID]
21
22 // Check if we have a wrapper object with a "query" field
23 var queryData any
24 var hasQueryWrapper bool
25
26 if jsonMap, ok := jsonData.(map[string]any); ok {
27 if query, exists := jsonMap["query"]; exists {
28 queryData = query
29 hasQueryWrapper = true
30 }
31 }
32
33 // If no query wrapper was found, use the entire input
34 if !hasQueryWrapper {
35 // If the input itself is not a valid query object, return it as is
36 if !isValidQueryObject(jsonData) {
37 return jsonData, nil
38 }
39 queryData = jsonData
40 } else if queryData == nil || !isValidQueryObject(queryData) {
41 // If we have a query wrapper but the query is nil or not a valid object,
42 // return the original data
43 return jsonData, nil
44 }
45
46 // Store rewrites if they exist
47 var oldRewrites any
48 if queryMap, ok := queryData.(map[string]any); ok {
49 if rewrites, exists := queryMap["rewrites"]; exists {
50 oldRewrites = rewrites
51 delete(queryMap, "rewrites")
52 }
53 }
54
55 // Convert input JSON to AST
56 jsonBytes, err := json.Marshal(queryData)
57 if err != nil {
58 return nil, fmt.Errorf("failed to marshal input JSON: %w", err)
59 }
60
61 node, err := parser.ParseJSON(jsonBytes)
62 if err != nil {
63 return nil, fmt.Errorf("failed to parse JSON into AST: %w", err)
64 }
65
66 // Store whether the input was a Token
67 isToken := false
68 var tokenWrap ast.Node
69 if token, ok := node.(*ast.Token); ok {
70 isToken = true
71 tokenWrap = token.Wrap
72 node = tokenWrap
73 }
74
75 // Store original node for rewrite if needed
76 var originalNode ast.Node
77 if opts.AddRewrites {
78 originalNode = node.Clone()
79 }
80
81 // Pre-check foundry/layer overrides to optimize processing
82 var patternFoundry, patternLayer, replacementFoundry, replacementLayer string
83 if opts.Direction { // true means AtoB
84 patternFoundry, patternLayer = opts.FoundryA, opts.LayerA
85 replacementFoundry, replacementLayer = opts.FoundryB, opts.LayerB
86 } else {
87 patternFoundry, patternLayer = opts.FoundryB, opts.LayerB
88 replacementFoundry, replacementLayer = opts.FoundryA, opts.LayerA
89 }
90
91 // Create a pattern cache key for memoization
92 type patternCacheKey struct {
93 ruleIndex int
94 foundry string
95 layer string
96 isReplacement bool
97 }
98 patternCache := make(map[patternCacheKey]ast.Node)
99
100 // Apply each rule to the AST
101 for i, rule := range rules {
102 // Create pattern and replacement based on direction
103 var pattern, replacement ast.Node
104 if opts.Direction { // true means AtoB
105 pattern = rule.Upper
106 replacement = rule.Lower
107 } else {
108 pattern = rule.Lower
109 replacement = rule.Upper
110 }
111
112 // Extract the inner nodes from the pattern and replacement tokens
113 if token, ok := pattern.(*ast.Token); ok {
114 pattern = token.Wrap
115 }
116 if token, ok := replacement.(*ast.Token); ok {
117 replacement = token.Wrap
118 }
119
Akron4de47a92025-06-27 11:58:11 +0200120 // Get or create pattern with overrides
121 patternKey := patternCacheKey{ruleIndex: i, foundry: patternFoundry, layer: patternLayer, isReplacement: false}
122 processedPattern, exists := patternCache[patternKey]
123 if !exists {
124 // Clone pattern only when needed
125 processedPattern = pattern.Clone()
126 // Apply foundry and layer overrides only if they're non-empty
127 if patternFoundry != "" || patternLayer != "" {
128 ast.ApplyFoundryAndLayerOverrides(processedPattern, patternFoundry, patternLayer)
129 }
130 patternCache[patternKey] = processedPattern
131 }
132
133 // Create a temporary matcher to check for actual matches
134 tempMatcher, err := matcher.NewMatcher(ast.Pattern{Root: processedPattern}, ast.Replacement{Root: &ast.Term{}})
135 if err != nil {
136 return nil, fmt.Errorf("failed to create temporary matcher: %w", err)
137 }
138
139 // Only proceed if there's an actual match
140 if !tempMatcher.Match(node) {
141 continue
142 }
143
144 // Get or create replacement with overrides (lazy evaluation)
145 replacementKey := patternCacheKey{ruleIndex: i, foundry: replacementFoundry, layer: replacementLayer, isReplacement: true}
146 processedReplacement, exists := patternCache[replacementKey]
147 if !exists {
148 // Clone replacement only when we have a match
149 processedReplacement = replacement.Clone()
150 // Apply foundry and layer overrides only if they're non-empty
151 if replacementFoundry != "" || replacementLayer != "" {
152 ast.ApplyFoundryAndLayerOverrides(processedReplacement, replacementFoundry, replacementLayer)
153 }
154 patternCache[replacementKey] = processedReplacement
155 }
156
157 // Create the actual matcher and apply replacement
158 actualMatcher, err := matcher.NewMatcher(ast.Pattern{Root: processedPattern}, ast.Replacement{Root: processedReplacement})
159 if err != nil {
160 return nil, fmt.Errorf("failed to create matcher: %w", err)
161 }
162 node = actualMatcher.Replace(node)
163 }
164
165 // Wrap the result in a token if the input was a token
166 var result ast.Node
167 if isToken {
168 result = &ast.Token{Wrap: node}
169 } else {
170 result = node
171 }
172
173 // Convert AST back to JSON
174 resultBytes, err := parser.SerializeToJSON(result)
175 if err != nil {
176 return nil, fmt.Errorf("failed to serialize AST to JSON: %w", err)
177 }
178
179 // Parse the JSON string back into
180 var resultData any
181 if err := json.Unmarshal(resultBytes, &resultData); err != nil {
182 return nil, fmt.Errorf("failed to parse result JSON: %w", err)
183 }
184
185 // Add rewrites if enabled and node was changed
186 if opts.AddRewrites && !ast.NodesEqual(node, originalNode) {
187 // Create rewrite object
188 rewrite := map[string]any{
189 "@type": "koral:rewrite",
190 "editor": "termMapper",
191 }
192
193 // Check if the node types are different (structural change)
194 if originalNode.Type() != node.Type() {
195 // Full node replacement
196 originalBytes, err := parser.SerializeToJSON(originalNode)
197 if err != nil {
198 return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
199 }
200 var originalJSON any
201 if err := json.Unmarshal(originalBytes, &originalJSON); err != nil {
202 return nil, fmt.Errorf("failed to parse original node JSON for rewrite: %w", err)
203 }
204 rewrite["original"] = originalJSON
205 } else if term, ok := originalNode.(*ast.Term); ok && ast.IsTermNode(node) {
206 // Check which attributes changed
207 newTerm := node.(*ast.Term)
208 if term.Foundry != newTerm.Foundry {
209 rewrite["scope"] = "foundry"
210 rewrite["original"] = term.Foundry
211 } else if term.Layer != newTerm.Layer {
212 rewrite["scope"] = "layer"
213 rewrite["original"] = term.Layer
214 } else if term.Key != newTerm.Key {
215 rewrite["scope"] = "key"
216 rewrite["original"] = term.Key
217 } else if term.Value != newTerm.Value {
218 rewrite["scope"] = "value"
219 rewrite["original"] = term.Value
220 } else {
221 // No specific attribute changed, use full node replacement
222 originalBytes, err := parser.SerializeToJSON(originalNode)
223 if err != nil {
224 return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
225 }
226 var originalJSON any
227 if err := json.Unmarshal(originalBytes, &originalJSON); err != nil {
228 return nil, fmt.Errorf("failed to parse original node JSON for rewrite: %w", err)
229 }
230 rewrite["original"] = originalJSON
231 }
232 } else {
233 // Full node replacement
234 originalBytes, err := parser.SerializeToJSON(originalNode)
235 if err != nil {
236 return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
237 }
238 var originalJSON any
239 if err := json.Unmarshal(originalBytes, &originalJSON); err != nil {
240 return nil, fmt.Errorf("failed to parse original node JSON for rewrite: %w", err)
241 }
242 rewrite["original"] = originalJSON
243 }
244
245 // Add rewrite to the node
246 if resultMap, ok := resultData.(map[string]any); ok {
247 if wrapMap, ok := resultMap["wrap"].(map[string]any); ok {
248 rewrites, exists := wrapMap["rewrites"]
249 if !exists {
250 rewrites = []any{}
251 }
252 if rewritesList, ok := rewrites.([]any); ok {
253 wrapMap["rewrites"] = append(rewritesList, rewrite)
254 } else {
255 wrapMap["rewrites"] = []any{rewrite}
256 }
257 }
258 }
259 }
260
261 // Restore rewrites if they existed
262 if oldRewrites != nil {
263 // Process old rewrites through AST to ensure backward compatibility
264 if rewritesList, ok := oldRewrites.([]any); ok {
265 processedRewrites := make([]any, len(rewritesList))
266 for i, rewriteData := range rewritesList {
267 // Marshal and unmarshal each rewrite to apply backward compatibility
268 rewriteBytes, err := json.Marshal(rewriteData)
269 if err != nil {
270 return nil, fmt.Errorf("failed to marshal old rewrite %d: %w", i, err)
271 }
272 var rewrite ast.Rewrite
273 if err := json.Unmarshal(rewriteBytes, &rewrite); err != nil {
274 return nil, fmt.Errorf("failed to unmarshal old rewrite %d: %w", i, err)
275 }
276 // Marshal back to get the transformed version
277 transformedBytes, err := json.Marshal(&rewrite)
278 if err != nil {
279 return nil, fmt.Errorf("failed to marshal transformed rewrite %d: %w", i, err)
280 }
281 var transformedRewrite any
282 if err := json.Unmarshal(transformedBytes, &transformedRewrite); err != nil {
283 return nil, fmt.Errorf("failed to unmarshal transformed rewrite %d: %w", i, err)
284 }
285 processedRewrites[i] = transformedRewrite
286 }
287 if resultMap, ok := resultData.(map[string]any); ok {
288 resultMap["rewrites"] = processedRewrites
289 }
290 } else {
291 // If it's not a list, restore as-is
292 if resultMap, ok := resultData.(map[string]any); ok {
293 resultMap["rewrites"] = oldRewrites
294 }
295 }
296 }
297
298 // If we had a query wrapper, put the transformed data back in it
299 if hasQueryWrapper {
300 if wrapper, ok := jsonData.(map[string]any); ok {
301 wrapper["query"] = resultData
302 return wrapper, nil
303 }
304 }
305
306 return resultData, nil
307}
308
309// isValidQueryObject checks if the query data is a valid object that can be processed
310func isValidQueryObject(data any) bool {
311 // Check if it's a map
312 queryMap, ok := data.(map[string]any)
313 if !ok {
314 return false
315 }
316
317 // Check if it has the required @type field
318 if _, ok := queryMap["@type"]; !ok {
319 return false
320 }
321
322 return true
323}