Skip to content

Commit 49748f1

Browse files
authored
Merge pull request #82 from michaeladler/feat/fast-curie-map
feat: add optimization for finding the best CURIE
2 parents 19254b3 + 0c3dbf0 commit 49748f1

File tree

1 file changed

+112
-22
lines changed

1 file changed

+112
-22
lines changed

ld/context.go

Lines changed: 112 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2015-2017 Piprate Limited
2+
// Copyright 2025 Siemens AG
23
//
34
// Licensed under the Apache License, Version 2.0 (the "License");
45
// you may not use this file except in compliance with the License.
@@ -47,6 +48,12 @@ type Context struct {
4748
inverse map[string]interface{}
4849
protected map[string]bool
4950
previousContext *Context
51+
fastCurieMap map[string]interface{}
52+
}
53+
54+
type FastCurieEntry struct {
55+
iri string
56+
terms []string
5057
}
5158

5259
// NewContext creates and returns a new Context object.
@@ -60,6 +67,7 @@ func NewContext(values map[string]interface{}, options *JsonLdOptions) *Context
6067
options: options,
6168
termDefinitions: make(map[string]interface{}),
6269
protected: make(map[string]bool),
70+
fastCurieMap: make(map[string]any),
6371
}
6472

6573
context.values["@base"] = options.Base
@@ -79,6 +87,7 @@ func (c *Context) AsMap() map[string]interface{} {
7987
"termDefinitions": c.termDefinitions,
8088
"inverse": c.inverse,
8189
"protected": c.protected,
90+
"fastCurieMap": c.fastCurieMap,
8291
}
8392
if c.previousContext != nil {
8493
res["previousContext"] = c.previousContext.AsMap()
@@ -628,6 +637,7 @@ func (c *Context) createTermDefinition(context map[string]interface{}, term stri
628637
termHasColon := colIndex > 0
629638

630639
definition["@reverse"] = false
640+
definition["termHasColon"] = termHasColon
631641

632642
// 11)
633643
if reverseValue, present := val["@reverse"]; present {
@@ -1391,39 +1401,56 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool
13911401
compactIRI := ""
13921402

13931403
// 5)
1394-
for term, termDefinitionVal := range c.termDefinitions {
1395-
if termDefinitionVal == nil {
1396-
continue
1404+
partialMatches := make([]FastCurieEntry, 0)
1405+
iriMap := c.fastCurieMap
1406+
// check for partial matches of against `iri`, which means look until
1407+
// iri.length - 1, not full length
1408+
maxPartialLength := len(iri) - 1
1409+
for i := 0; i < maxPartialLength; i++ {
1410+
iriAny, ok := iriMap[string(iri[i])]
1411+
if !ok {
1412+
break
13971413
}
1398-
1399-
// 5.1)
1400-
if strings.Contains(term, ":") {
1401-
continue
1414+
iriMap = iriAny.(map[string]interface{})
1415+
if arrAny, ok := iriMap[""]; ok {
1416+
entry := arrAny.([]FastCurieEntry)[0]
1417+
partialMatches = append(partialMatches, entry)
14021418
}
1419+
}
1420+
// check partial matches in reverse order to prefer longest ones first
1421+
for i := len(partialMatches) - 1; i >= 0; i-- {
1422+
entry := partialMatches[i]
1423+
for _, term := range entry.terms {
1424+
termDefinitionAny, ok := c.termDefinitions[term]
1425+
if !ok {
1426+
continue
1427+
}
1428+
termDef := termDefinitionAny.(map[string]interface{})
14031429

1404-
// 5.2)
1405-
termDefinition := termDefinitionVal.(map[string]interface{})
1406-
idStr := termDefinition["@id"].(string)
1407-
if iri == idStr || !strings.HasPrefix(iri, idStr) {
1408-
continue
1409-
}
1430+
// a CURIE is usable if:
1431+
// 1. it has no mapping, OR
1432+
// 2. value is null, which means we're not compacting an @value, AND
1433+
// the mapping matches the IRI
1434+
curie := term + ":" + iri[len(entry.iri):]
1435+
prefix, hasPrefix := termDef["_prefix"]
1436+
curieMapping, hasCurie := c.termDefinitions[curie]
1437+
1438+
isUsableCurie := hasPrefix && prefix.(bool) && (!hasCurie || value == nil && curieMapping.(map[string]any)["@id"].(string) == iri)
14101439

1411-
// 5.3)
1412-
candidate := term + ":" + iri[len(idStr):]
1413-
// 5.4)
1414-
candidateVal, containsCandidate := c.termDefinitions[candidate]
1415-
prefix, hasPrefix := termDefinition["_prefix"]
1416-
if (compactIRI == "" || CompareShortestLeast(candidate, compactIRI)) && hasPrefix && prefix.(bool) &&
1417-
(!containsCandidate ||
1418-
(iri == candidateVal.(map[string]interface{})["@id"] && value == nil)) {
1419-
compactIRI = candidate
1440+
// select curie if it is shorter or the same length but lexicographically
1441+
// less than the current choice
1442+
if isUsableCurie && (compactIRI == "" || CompareShortestLeast(curie, compactIRI)) {
1443+
compactIRI = curie
1444+
}
14201445
}
14211446
}
14221447

14231448
if compactIRI != "" {
14241449
return compactIRI, nil
14251450
}
14261451

1452+
// If iri could be confused with a compact IRI using a term in this context,
1453+
// signal an error
14271454
for term, td := range c.termDefinitions {
14281455
if tdMap, isMap := td.(map[string]interface{}); isMap {
14291456
prefix, hasPrefix := tdMap["_prefix"]
@@ -1433,10 +1460,12 @@ func (c *Context) CompactIri(iri string, value interface{}, relativeToVocab bool
14331460
}
14341461
}
14351462

1463+
// compact IRI relative to base
14361464
if !relativeToVocab {
14371465
return RemoveBase(c.values["@base"], iri), nil
14381466
}
14391467

1468+
// return IRI as is
14401469
return iri, nil
14411470
}
14421471

@@ -1499,6 +1528,9 @@ func (c *Context) GetInverse() map[string]interface{} {
14991528
terms := GetKeys(c.termDefinitions)
15001529
sort.Sort(ShortestLeast(terms))
15011530

1531+
// variables for building fast CURIE map
1532+
irisToTerms := make(map[string][]string, 0)
1533+
15021534
for _, term := range terms {
15031535
definitionVal := c.termDefinitions[term]
15041536
// 3.1)
@@ -1528,11 +1560,31 @@ func (c *Context) GetInverse() map[string]interface{} {
15281560
// 3.4 + 3.5)
15291561
var containerMap map[string]interface{}
15301562
containerMapVal, present := c.inverse[iri]
1563+
isKeyword := IsKeyword(iri)
1564+
termHasColon := definition["termHasColon"].(bool)
15311565
if !present {
15321566
containerMap = make(map[string]interface{})
15331567
c.inverse[iri] = containerMap
1568+
1569+
if !isKeyword && !termHasColon {
1570+
// init IRI to term map and fast CURIE map
1571+
irisToTerms[iri] = []string{term}
1572+
entry := FastCurieEntry{iri: iri, terms: irisToTerms[iri]}
1573+
letter := string(iri[0])
1574+
if val, ok := c.fastCurieMap[letter]; ok {
1575+
arr := val.([]FastCurieEntry)
1576+
arr = append(arr, entry)
1577+
c.fastCurieMap[letter] = arr
1578+
} else {
1579+
c.fastCurieMap[letter] = []FastCurieEntry{entry}
1580+
}
1581+
}
15341582
} else {
15351583
containerMap = containerMapVal.(map[string]interface{})
1584+
if !isKeyword && !termHasColon {
1585+
// add IRI to term match
1586+
irisToTerms[iri] = append(irisToTerms[iri], term)
1587+
}
15361588
}
15371589

15381590
// 3.6 + 3.7)
@@ -1649,10 +1701,48 @@ func (c *Context) GetInverse() map[string]interface{} {
16491701
}
16501702
}
16511703

1704+
// build fast CURIE map
1705+
for key := range c.fastCurieMap {
1706+
buildIriMap(c.fastCurieMap, key, 1)
1707+
}
1708+
16521709
// 4)
16531710
return c.inverse
16541711
}
16551712

1713+
// buildIriMap runs a recursive algorithm to build a lookup map for quickly finding
1714+
// potential CURIEs.
1715+
//
1716+
// iriMap is the map to build.
1717+
// key is the current key in the map to work on.
1718+
// idx is the index into the IRI to compare.
1719+
func buildIriMap(iriMap map[string]interface{}, key string, idx int) {
1720+
entries := iriMap[key].([]FastCurieEntry)
1721+
next := make(map[string]interface{}, 0)
1722+
iriMap[key] = next
1723+
1724+
for _, entry := range entries {
1725+
letter := ""
1726+
iri := entry.iri
1727+
if idx < len(iri) {
1728+
letter = string(iri[idx])
1729+
}
1730+
if val, ok := next[letter]; ok {
1731+
arr := val.([]FastCurieEntry)
1732+
arr = append(arr, entry)
1733+
next[letter] = arr
1734+
} else {
1735+
next[letter] = []FastCurieEntry{entry}
1736+
}
1737+
}
1738+
for key := range next {
1739+
if key == "" {
1740+
continue
1741+
}
1742+
buildIriMap(next, key, idx+1)
1743+
}
1744+
}
1745+
16561746
// SelectTerm picks the preferred compaction term from the inverse context entry.
16571747
// See http://www.w3.org/TR/json-ld-api/#term-selection
16581748
//

0 commit comments

Comments
 (0)