123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- // Copyright 2013 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 language
- import "errors"
- type scriptRegionFlags uint8
- const (
- isList = 1 << iota
- scriptInFrom
- regionInFrom
- )
- func (t *Tag) setUndefinedLang(id Language) {
- if t.LangID == 0 {
- t.LangID = id
- }
- }
- func (t *Tag) setUndefinedScript(id Script) {
- if t.ScriptID == 0 {
- t.ScriptID = id
- }
- }
- func (t *Tag) setUndefinedRegion(id Region) {
- if t.RegionID == 0 || t.RegionID.Contains(id) {
- t.RegionID = id
- }
- }
- // ErrMissingLikelyTagsData indicates no information was available
- // to compute likely values of missing tags.
- var ErrMissingLikelyTagsData = errors.New("missing likely tags data")
- // addLikelySubtags sets subtags to their most likely value, given the locale.
- // In most cases this means setting fields for unknown values, but in some
- // cases it may alter a value. It returns an ErrMissingLikelyTagsData error
- // if the given locale cannot be expanded.
- func (t Tag) addLikelySubtags() (Tag, error) {
- id, err := addTags(t)
- if err != nil {
- return t, err
- } else if id.equalTags(t) {
- return t, nil
- }
- id.RemakeString()
- return id, nil
- }
- // specializeRegion attempts to specialize a group region.
- func specializeRegion(t *Tag) bool {
- if i := regionInclusion[t.RegionID]; i < nRegionGroups {
- x := likelyRegionGroup[i]
- if Language(x.lang) == t.LangID && Script(x.script) == t.ScriptID {
- t.RegionID = Region(x.region)
- }
- return true
- }
- return false
- }
- // Maximize returns a new tag with missing tags filled in.
- func (t Tag) Maximize() (Tag, error) {
- return addTags(t)
- }
- func addTags(t Tag) (Tag, error) {
- // We leave private use identifiers alone.
- if t.IsPrivateUse() {
- return t, nil
- }
- if t.ScriptID != 0 && t.RegionID != 0 {
- if t.LangID != 0 {
- // already fully specified
- specializeRegion(&t)
- return t, nil
- }
- // Search matches for und-script-region. Note that for these cases
- // region will never be a group so there is no need to check for this.
- list := likelyRegion[t.RegionID : t.RegionID+1]
- if x := list[0]; x.flags&isList != 0 {
- list = likelyRegionList[x.lang : x.lang+uint16(x.script)]
- }
- for _, x := range list {
- // Deviating from the spec. See match_test.go for details.
- if Script(x.script) == t.ScriptID {
- t.setUndefinedLang(Language(x.lang))
- return t, nil
- }
- }
- }
- if t.LangID != 0 {
- // Search matches for lang-script and lang-region, where lang != und.
- if t.LangID < langNoIndexOffset {
- x := likelyLang[t.LangID]
- if x.flags&isList != 0 {
- list := likelyLangList[x.region : x.region+uint16(x.script)]
- if t.ScriptID != 0 {
- for _, x := range list {
- if Script(x.script) == t.ScriptID && x.flags&scriptInFrom != 0 {
- t.setUndefinedRegion(Region(x.region))
- return t, nil
- }
- }
- } else if t.RegionID != 0 {
- count := 0
- goodScript := true
- tt := t
- for _, x := range list {
- // We visit all entries for which the script was not
- // defined, including the ones where the region was not
- // defined. This allows for proper disambiguation within
- // regions.
- if x.flags&scriptInFrom == 0 && t.RegionID.Contains(Region(x.region)) {
- tt.RegionID = Region(x.region)
- tt.setUndefinedScript(Script(x.script))
- goodScript = goodScript && tt.ScriptID == Script(x.script)
- count++
- }
- }
- if count == 1 {
- return tt, nil
- }
- // Even if we fail to find a unique Region, we might have
- // an unambiguous script.
- if goodScript {
- t.ScriptID = tt.ScriptID
- }
- }
- }
- }
- } else {
- // Search matches for und-script.
- if t.ScriptID != 0 {
- x := likelyScript[t.ScriptID]
- if x.region != 0 {
- t.setUndefinedRegion(Region(x.region))
- t.setUndefinedLang(Language(x.lang))
- return t, nil
- }
- }
- // Search matches for und-region. If und-script-region exists, it would
- // have been found earlier.
- if t.RegionID != 0 {
- if i := regionInclusion[t.RegionID]; i < nRegionGroups {
- x := likelyRegionGroup[i]
- if x.region != 0 {
- t.setUndefinedLang(Language(x.lang))
- t.setUndefinedScript(Script(x.script))
- t.RegionID = Region(x.region)
- }
- } else {
- x := likelyRegion[t.RegionID]
- if x.flags&isList != 0 {
- x = likelyRegionList[x.lang]
- }
- if x.script != 0 && x.flags != scriptInFrom {
- t.setUndefinedLang(Language(x.lang))
- t.setUndefinedScript(Script(x.script))
- return t, nil
- }
- }
- }
- }
- // Search matches for lang.
- if t.LangID < langNoIndexOffset {
- x := likelyLang[t.LangID]
- if x.flags&isList != 0 {
- x = likelyLangList[x.region]
- }
- if x.region != 0 {
- t.setUndefinedScript(Script(x.script))
- t.setUndefinedRegion(Region(x.region))
- }
- specializeRegion(&t)
- if t.LangID == 0 {
- t.LangID = _en // default language
- }
- return t, nil
- }
- return t, ErrMissingLikelyTagsData
- }
- func (t *Tag) setTagsFrom(id Tag) {
- t.LangID = id.LangID
- t.ScriptID = id.ScriptID
- t.RegionID = id.RegionID
- }
- // minimize removes the region or script subtags from t such that
- // t.addLikelySubtags() == t.minimize().addLikelySubtags().
- func (t Tag) minimize() (Tag, error) {
- t, err := minimizeTags(t)
- if err != nil {
- return t, err
- }
- t.RemakeString()
- return t, nil
- }
- // minimizeTags mimics the behavior of the ICU 51 C implementation.
- func minimizeTags(t Tag) (Tag, error) {
- if t.equalTags(Und) {
- return t, nil
- }
- max, err := addTags(t)
- if err != nil {
- return t, err
- }
- for _, id := range [...]Tag{
- {LangID: t.LangID},
- {LangID: t.LangID, RegionID: t.RegionID},
- {LangID: t.LangID, ScriptID: t.ScriptID},
- } {
- if x, err := addTags(id); err == nil && max.equalTags(x) {
- t.setTagsFrom(id)
- break
- }
- }
- return t, nil
- }
|