165 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2020 The Gitea Authors. All rights reserved.
 | |
| // Copyright 2015 Kenneth Shaw
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package emoji
 | |
| 
 | |
| import (
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| )
 | |
| 
 | |
| // Gemoji is a set of emoji data.
 | |
| type Gemoji []Emoji
 | |
| 
 | |
| // Emoji represents a single emoji and associated data.
 | |
| type Emoji struct {
 | |
| 	Emoji          string
 | |
| 	Description    string
 | |
| 	Aliases        []string
 | |
| 	UnicodeVersion string
 | |
| 	SkinTones      bool
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	// codeMap provides a map of the emoji unicode code to its emoji data.
 | |
| 	codeMap map[string]int
 | |
| 
 | |
| 	// aliasMap provides a map of the alias to its emoji data.
 | |
| 	aliasMap map[string]int
 | |
| 
 | |
| 	// codeReplacer is the string replacer for emoji codes.
 | |
| 	codeReplacer *strings.Replacer
 | |
| 
 | |
| 	// aliasReplacer is the string replacer for emoji aliases.
 | |
| 	aliasReplacer *strings.Replacer
 | |
| 
 | |
| 	once sync.Once
 | |
| )
 | |
| 
 | |
| func loadMap() {
 | |
| 
 | |
| 	once.Do(func() {
 | |
| 
 | |
| 		// initialize
 | |
| 		codeMap = make(map[string]int, len(GemojiData))
 | |
| 		aliasMap = make(map[string]int, len(GemojiData))
 | |
| 
 | |
| 		// process emoji codes and aliases
 | |
| 		codePairs := make([]string, 0)
 | |
| 		aliasPairs := make([]string, 0)
 | |
| 
 | |
| 		// sort from largest to small so we match combined emoji first
 | |
| 		sort.Slice(GemojiData, func(i, j int) bool {
 | |
| 			return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
 | |
| 		})
 | |
| 
 | |
| 		for i, e := range GemojiData {
 | |
| 			if e.Emoji == "" || len(e.Aliases) == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			// setup codes
 | |
| 			codeMap[e.Emoji] = i
 | |
| 			codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":")
 | |
| 
 | |
| 			// setup aliases
 | |
| 			for _, a := range e.Aliases {
 | |
| 				if a == "" {
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				aliasMap[a] = i
 | |
| 				aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// create replacers
 | |
| 		codeReplacer = strings.NewReplacer(codePairs...)
 | |
| 		aliasReplacer = strings.NewReplacer(aliasPairs...)
 | |
| 	})
 | |
| 
 | |
| }
 | |
| 
 | |
| // FromCode retrieves the emoji data based on the provided unicode code (ie,
 | |
| // "\u2618" will return the Gemoji data for "shamrock").
 | |
| func FromCode(code string) *Emoji {
 | |
| 	loadMap()
 | |
| 	i, ok := codeMap[code]
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return &GemojiData[i]
 | |
| }
 | |
| 
 | |
| // FromAlias retrieves the emoji data based on the provided alias in the form
 | |
| // "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji
 | |
| // data for "shamrock").
 | |
| func FromAlias(alias string) *Emoji {
 | |
| 	loadMap()
 | |
| 	if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
 | |
| 		alias = alias[1 : len(alias)-1]
 | |
| 	}
 | |
| 
 | |
| 	i, ok := aliasMap[alias]
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return &GemojiData[i]
 | |
| }
 | |
| 
 | |
| // ReplaceCodes replaces all emoji codes with the first corresponding emoji
 | |
| // alias (in the form of ":alias:") (ie, "\u2618" will be converted to
 | |
| // ":shamrock:").
 | |
| func ReplaceCodes(s string) string {
 | |
| 	loadMap()
 | |
| 	return codeReplacer.Replace(s)
 | |
| }
 | |
| 
 | |
| // ReplaceAliases replaces all aliases of the form ":alias:" with its
 | |
| // corresponding unicode value.
 | |
| func ReplaceAliases(s string) string {
 | |
| 	loadMap()
 | |
| 	return aliasReplacer.Replace(s)
 | |
| }
 | |
| 
 | |
| // FindEmojiSubmatchIndex returns index pair of longest emoji in a string
 | |
| func FindEmojiSubmatchIndex(s string) []int {
 | |
| 	loadMap()
 | |
| 	found := make(map[int]int)
 | |
| 	keys := make([]int, 0)
 | |
| 
 | |
| 	//see if there are any emoji in string before looking for position of specific ones
 | |
| 	//no performance difference when there is a match but 10x faster when there are not
 | |
| 	if s == ReplaceCodes(s) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// get index of first emoji occurrence while also checking for longest combination
 | |
| 	for j := range GemojiData {
 | |
| 		i := strings.Index(s, GemojiData[j].Emoji)
 | |
| 		if i != -1 {
 | |
| 			if _, ok := found[i]; !ok {
 | |
| 				if len(keys) == 0 || i < keys[0] {
 | |
| 					found[i] = j
 | |
| 					keys = []int{i}
 | |
| 				}
 | |
| 				if i == 0 {
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(keys) > 0 {
 | |
| 		index := keys[0]
 | |
| 		return []int{index, index + len(GemojiData[found[index]].Emoji)}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |