| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package display |
| |
| // This file contains common lookup code that is shared between the various |
| // implementations of Namer and Dictionaries. |
| |
| import ( |
| "fmt" |
| "sort" |
| "strings" |
| |
| "golang.org/x/text/language" |
| ) |
| |
| type namer interface { |
| // name gets the string for the given index. It should walk the |
| // inheritance chain if a value is not present in the base index. |
| name(idx int) string |
| } |
| |
| func nameLanguage(n namer, x interface{}) string { |
| t, _ := language.All.Compose(x) |
| for { |
| i, _, _ := langTagSet.index(t.Raw()) |
| if s := n.name(i); s != "" { |
| return s |
| } |
| if t = t.Parent(); t == language.Und { |
| return "" |
| } |
| } |
| } |
| |
| func nameScript(n namer, x interface{}) string { |
| t, _ := language.DeprecatedScript.Compose(x) |
| _, s, _ := t.Raw() |
| return n.name(scriptIndex.index(s.String())) |
| } |
| |
| func nameRegion(n namer, x interface{}) string { |
| t, _ := language.DeprecatedRegion.Compose(x) |
| _, _, r := t.Raw() |
| return n.name(regionIndex.index(r.String())) |
| } |
| |
| func nameTag(langN, scrN, regN namer, x interface{}) string { |
| t, ok := x.(language.Tag) |
| if !ok { |
| return "" |
| } |
| const form = language.All &^ language.SuppressScript |
| if c, err := form.Canonicalize(t); err == nil { |
| t = c |
| } |
| _, sRaw, rRaw := t.Raw() |
| i, scr, reg := langTagSet.index(t.Raw()) |
| for i != -1 { |
| if str := langN.name(i); str != "" { |
| if hasS, hasR := (scr != language.Script{}), (reg != language.Region{}); hasS || hasR { |
| ss, sr := "", "" |
| if hasS { |
| ss = scrN.name(scriptIndex.index(scr.String())) |
| } |
| if hasR { |
| sr = regN.name(regionIndex.index(reg.String())) |
| } |
| // TODO: use patterns in CLDR or at least confirm they are the |
| // same for all languages. |
| if ss != "" && sr != "" { |
| return fmt.Sprintf("%s (%s, %s)", str, ss, sr) |
| } |
| if ss != "" || sr != "" { |
| return fmt.Sprintf("%s (%s%s)", str, ss, sr) |
| } |
| } |
| return str |
| } |
| scr, reg = sRaw, rRaw |
| if t = t.Parent(); t == language.Und { |
| return "" |
| } |
| i, _, _ = langTagSet.index(t.Raw()) |
| } |
| return "" |
| } |
| |
| // header contains the data and indexes for a single namer. |
| // data contains a series of strings concatenated into one. index contains the |
| // offsets for a string in data. For example, consider a header that defines |
| // strings for the languages de, el, en, fi, and nl: |
| // |
| // header{ |
| // data: "GermanGreekEnglishDutch", |
| // index: []uint16{0, 6, 11, 18, 18, 23}, |
| // } |
| // |
| // For a language with index i, the string is defined by |
| // data[index[i]:index[i+1]]. So the number of elements in index is always one |
| // greater than the number of languages for which header defines a value. |
| // A string for a language may be empty, which means the name is undefined. In |
| // the above example, the name for fi (Finnish) is undefined. |
| type header struct { |
| data string |
| index []uint16 |
| } |
| |
| // name looks up the name for a tag in the dictionary, given its index. |
| func (h *header) name(i int) string { |
| if 0 <= i && i < len(h.index)-1 { |
| return h.data[h.index[i]:h.index[i+1]] |
| } |
| return "" |
| } |
| |
| // tagSet is used to find the index of a language in a set of tags. |
| type tagSet struct { |
| single tagIndex |
| long []string |
| } |
| |
| var ( |
| langTagSet = tagSet{ |
| single: langIndex, |
| long: langTagsLong, |
| } |
| |
| // selfTagSet is used for indexing the language strings in their own |
| // language. |
| selfTagSet = tagSet{ |
| single: selfIndex, |
| long: selfTagsLong, |
| } |
| |
| zzzz = language.MustParseScript("Zzzz") |
| zz = language.MustParseRegion("ZZ") |
| ) |
| |
| // index returns the index of the tag for the given base, script and region or |
| // its parent if the tag is not available. If the match is for a parent entry, |
| // the excess script and region are returned. |
| func (ts *tagSet) index(base language.Base, scr language.Script, reg language.Region) (int, language.Script, language.Region) { |
| lang := base.String() |
| index := -1 |
| if (scr != language.Script{} || reg != language.Region{}) { |
| if scr == zzzz { |
| scr = language.Script{} |
| } |
| if reg == zz { |
| reg = language.Region{} |
| } |
| |
| i := sort.SearchStrings(ts.long, lang) |
| // All entries have either a script or a region and not both. |
| scrStr, regStr := scr.String(), reg.String() |
| for ; i < len(ts.long) && strings.HasPrefix(ts.long[i], lang); i++ { |
| if s := ts.long[i][len(lang)+1:]; s == scrStr { |
| scr = language.Script{} |
| index = i + ts.single.len() |
| break |
| } else if s == regStr { |
| reg = language.Region{} |
| index = i + ts.single.len() |
| break |
| } |
| } |
| } |
| if index == -1 { |
| index = ts.single.index(lang) |
| } |
| return index, scr, reg |
| } |
| |
| func (ts *tagSet) Tags() []language.Tag { |
| tags := make([]language.Tag, 0, ts.single.len()+len(ts.long)) |
| ts.single.keys(func(s string) { |
| tags = append(tags, language.Raw.MustParse(s)) |
| }) |
| for _, s := range ts.long { |
| tags = append(tags, language.Raw.MustParse(s)) |
| } |
| return tags |
| } |
| |
| func supportedScripts() []language.Script { |
| scr := make([]language.Script, 0, scriptIndex.len()) |
| scriptIndex.keys(func(s string) { |
| scr = append(scr, language.MustParseScript(s)) |
| }) |
| return scr |
| } |
| |
| func supportedRegions() []language.Region { |
| reg := make([]language.Region, 0, regionIndex.len()) |
| regionIndex.keys(func(s string) { |
| reg = append(reg, language.MustParseRegion(s)) |
| }) |
| return reg |
| } |
| |
| // tagIndex holds a concatenated lists of subtags of length 2 to 4, one string |
| // for each length, which can be used in combination with binary search to get |
| // the index associated with a tag. |
| // For example, a tagIndex{ |
| // |
| // "arenesfrruzh", // 6 2-byte tags. |
| // "barwae", // 2 3-byte tags. |
| // "", |
| // |
| // } |
| // would mean that the 2-byte tag "fr" had an index of 3, and the 3-byte tag |
| // "wae" had an index of 7. |
| type tagIndex [3]string |
| |
| func (t *tagIndex) index(s string) int { |
| sz := len(s) |
| if sz < 2 || 4 < sz { |
| return -1 |
| } |
| a := t[sz-2] |
| index := sort.Search(len(a)/sz, func(i int) bool { |
| p := i * sz |
| return a[p:p+sz] >= s |
| }) |
| p := index * sz |
| if end := p + sz; end > len(a) || a[p:end] != s { |
| return -1 |
| } |
| // Add the number of tags for smaller sizes. |
| for i := 0; i < sz-2; i++ { |
| index += len(t[i]) / (i + 2) |
| } |
| return index |
| } |
| |
| // len returns the number of tags that are contained in the tagIndex. |
| func (t *tagIndex) len() (n int) { |
| for i, s := range t { |
| n += len(s) / (i + 2) |
| } |
| return n |
| } |
| |
| // keys calls f for each tag. |
| func (t *tagIndex) keys(f func(key string)) { |
| for i, s := range *t { |
| for ; s != ""; s = s[i+2:] { |
| f(s[:i+2]) |
| } |
| } |
| } |