123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- // 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 (
- "io"
- "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
-
- // emptyReplacer is the string replacer for emoji codes.
- emptyReplacer *strings.Replacer
-
- // 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)
- emptyPairs := 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]+":")
- emptyPairs = append(emptyPairs, e.Emoji, e.Emoji)
-
- // setup aliases
- for _, a := range e.Aliases {
- if a == "" {
- continue
- }
-
- aliasMap[a] = i
- aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
- }
- }
-
- // create replacers
- emptyReplacer = strings.NewReplacer(emptyPairs...)
- 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)
- }
-
- type rememberSecondWriteWriter struct {
- pos int
- idx int
- end int
- writecount int
- }
-
- func (n *rememberSecondWriteWriter) Write(p []byte) (int, error) {
- n.writecount++
- if n.writecount == 2 {
- n.idx = n.pos
- n.end = n.pos + len(p)
- n.pos += len(p)
- return len(p), io.EOF
- }
- n.pos += len(p)
- return len(p), nil
- }
-
- func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) {
- n.writecount++
- if n.writecount == 2 {
- n.idx = n.pos
- n.end = n.pos + len(s)
- n.pos += len(s)
- return len(s), io.EOF
- }
- n.pos += len(s)
- return len(s), nil
- }
-
- // FindEmojiSubmatchIndex returns index pair of longest emoji in a string
- func FindEmojiSubmatchIndex(s string) []int {
- loadMap()
- secondWriteWriter := rememberSecondWriteWriter{}
-
- // A faster and clean implementation would copy the trie tree formation in strings.NewReplacer but
- // we can be lazy here.
- //
- // The implementation of strings.Replacer.WriteString is such that the first index of the emoji
- // submatch is simply the second thing that is written to WriteString in the writer.
- //
- // Therefore we can simply take the index of the second write as our first emoji
- //
- // FIXME: just copy the trie implementation from strings.NewReplacer
- _, _ = emptyReplacer.WriteString(&secondWriteWriter, s)
-
- // if we wrote less than twice then we never "replaced"
- if secondWriteWriter.writecount < 2 {
- return nil
- }
-
- return []int{secondWriteWriter.idx, secondWriteWriter.end}
- }
|