github.com/prebid/prebid-server/v2@v2.18.0/macros/string_index_based_replacer.go (about) 1 package macros 2 3 import ( 4 "strings" 5 "sync" 6 ) 7 8 const ( 9 delimiter = "##" 10 ) 11 12 type stringIndexBasedReplacer struct { 13 templates map[string]urlMetaTemplate 14 sync.RWMutex 15 } 16 17 type urlMetaTemplate struct { 18 startingIndices []int 19 endingIndices []int 20 } 21 22 // NewStringIndexBasedReplacer will return instance of string index based macro replacer 23 func NewStringIndexBasedReplacer() Replacer { 24 return &stringIndexBasedReplacer{ 25 templates: make(map[string]urlMetaTemplate), 26 } 27 } 28 29 // constructTemplate func finds index bounds of all macros in an input string where macro format is ##data##. 30 // constructTemplate func returns two arrays with start indexes and end indexes for all macros found in the input string. 31 // Start index of the macro points to the index of the delimiter(##) start. 32 // End index of the macro points to the end index of the delimiter. 33 // For the valid input string number of start and end indexes should be equal, and they should not intersect. 34 // This approach shows better performance results compare to standard GoLang string replacer. 35 func constructTemplate(url string) urlMetaTemplate { 36 currentIndex := 0 37 tmplt := urlMetaTemplate{ 38 startingIndices: []int{}, 39 endingIndices: []int{}, 40 } 41 delimiterLen := len(delimiter) 42 for { 43 currentIndex = currentIndex + strings.Index(url[currentIndex:], delimiter) 44 if currentIndex == -1 { 45 break 46 } 47 startIndex := currentIndex + delimiterLen 48 endingIndex := strings.Index(url[startIndex:], delimiter) 49 if endingIndex == -1 { 50 break 51 } 52 endingIndex = endingIndex + startIndex - 1 53 tmplt.startingIndices = append(tmplt.startingIndices, startIndex) 54 tmplt.endingIndices = append(tmplt.endingIndices, endingIndex) 55 currentIndex = endingIndex + delimiterLen + 1 56 if currentIndex >= len(url)-1 { 57 break 58 } 59 } 60 return tmplt 61 } 62 63 // Replace function replaces macros in a given string with the data from macroProvider and returns modified input string. 64 // If a given string was previously processed this function fetches its metadata from the cache. 65 // If input string is not found in cache then template metadata will be created. 66 // Iterates over start and end indexes of the template arrays and extracts macro name from the input string. 67 // Gets the value of the extracted macro from the macroProvider. Replaces macro with corresponding value. 68 func (s *stringIndexBasedReplacer) Replace(result *strings.Builder, url string, macroProvider *MacroProvider) { 69 template := s.getTemplate(url) 70 currentIndex := 0 71 delimLen := len(delimiter) 72 for i, index := range template.startingIndices { 73 macro := url[index : template.endingIndices[i]+1] 74 // copy prev part 75 result.WriteString(url[currentIndex : index-delimLen]) 76 value := macroProvider.GetMacro(macro) 77 if value != "" { 78 result.WriteString(value) 79 } 80 currentIndex = index + len(macro) + delimLen 81 } 82 result.WriteString(url[currentIndex:]) 83 } 84 85 func (s *stringIndexBasedReplacer) getTemplate(url string) urlMetaTemplate { 86 var ( 87 template urlMetaTemplate 88 ok bool 89 ) 90 s.RLock() 91 template, ok = s.templates[url] 92 s.RUnlock() 93 94 if !ok { 95 s.Lock() 96 template = constructTemplate(url) 97 s.templates[url] = template 98 s.Unlock() 99 } 100 return template 101 }