github.com/prebid/prebid-server@v0.275.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(url string, macroProvider *macroProvider) (string, error) { 69 tmplt := s.getTemplate(url) 70 71 var result strings.Builder 72 currentIndex := 0 73 delimLen := len(delimiter) 74 for i, index := range tmplt.startingIndices { 75 macro := url[index : tmplt.endingIndices[i]+1] 76 // copy prev part 77 result.WriteString(url[currentIndex : index-delimLen]) 78 value := macroProvider.GetMacro(macro) 79 if value != "" { 80 result.WriteString(value) 81 } 82 currentIndex = index + len(macro) + delimLen 83 } 84 result.WriteString(url[currentIndex:]) 85 return result.String(), nil 86 } 87 88 func (s *stringIndexBasedReplacer) getTemplate(url string) urlMetaTemplate { 89 var ( 90 template urlMetaTemplate 91 ok bool 92 ) 93 s.RLock() 94 template, ok = s.templates[url] 95 s.RUnlock() 96 97 if !ok { 98 s.Lock() 99 template = constructTemplate(url) 100 s.templates[url] = template 101 s.Unlock() 102 } 103 return template 104 }