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  }