| // 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 cldr |
| |
| // This file implements the various inheritance constructs defined by LDML. |
| // See https://www.unicode.org/reports/tr35/#Inheritance_and_Validity |
| // for more details. |
| |
| import ( |
| "fmt" |
| "log" |
| "reflect" |
| "regexp" |
| "sort" |
| "strings" |
| ) |
| |
| // fieldIter iterates over fields in a struct. It includes |
| // fields of embedded structs. |
| type fieldIter struct { |
| v reflect.Value |
| index, n []int |
| } |
| |
| func iter(v reflect.Value) fieldIter { |
| if v.Kind() != reflect.Struct { |
| log.Panicf("value %v must be a struct", v) |
| } |
| i := fieldIter{ |
| v: v, |
| index: []int{0}, |
| n: []int{v.NumField()}, |
| } |
| i.descent() |
| return i |
| } |
| |
| func (i *fieldIter) descent() { |
| for f := i.field(); f.Anonymous && f.Type.NumField() > 0; f = i.field() { |
| i.index = append(i.index, 0) |
| i.n = append(i.n, f.Type.NumField()) |
| } |
| } |
| |
| func (i *fieldIter) done() bool { |
| return len(i.index) == 1 && i.index[0] >= i.n[0] |
| } |
| |
| func skip(f reflect.StructField) bool { |
| return !f.Anonymous && (f.Name[0] < 'A' || f.Name[0] > 'Z') |
| } |
| |
| func (i *fieldIter) next() { |
| for { |
| k := len(i.index) - 1 |
| i.index[k]++ |
| if i.index[k] < i.n[k] { |
| if !skip(i.field()) { |
| break |
| } |
| } else { |
| if k == 0 { |
| return |
| } |
| i.index = i.index[:k] |
| i.n = i.n[:k] |
| } |
| } |
| i.descent() |
| } |
| |
| func (i *fieldIter) value() reflect.Value { |
| return i.v.FieldByIndex(i.index) |
| } |
| |
| func (i *fieldIter) field() reflect.StructField { |
| return i.v.Type().FieldByIndex(i.index) |
| } |
| |
| type visitor func(v reflect.Value) error |
| |
| var stopDescent = fmt.Errorf("do not recurse") |
| |
| func (f visitor) visit(x interface{}) error { |
| return f.visitRec(reflect.ValueOf(x)) |
| } |
| |
| // visit recursively calls f on all nodes in v. |
| func (f visitor) visitRec(v reflect.Value) error { |
| if v.Kind() == reflect.Ptr { |
| if v.IsNil() { |
| return nil |
| } |
| return f.visitRec(v.Elem()) |
| } |
| if err := f(v); err != nil { |
| if err == stopDescent { |
| return nil |
| } |
| return err |
| } |
| switch v.Kind() { |
| case reflect.Struct: |
| for i := iter(v); !i.done(); i.next() { |
| if err := f.visitRec(i.value()); err != nil { |
| return err |
| } |
| } |
| case reflect.Slice: |
| for i := 0; i < v.Len(); i++ { |
| if err := f.visitRec(v.Index(i)); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // getPath is used for error reporting purposes only. |
| func getPath(e Elem) string { |
| if e == nil { |
| return "<nil>" |
| } |
| if e.enclosing() == nil { |
| return e.GetCommon().name |
| } |
| if e.GetCommon().Type == "" { |
| return fmt.Sprintf("%s.%s", getPath(e.enclosing()), e.GetCommon().name) |
| } |
| return fmt.Sprintf("%s.%s[type=%s]", getPath(e.enclosing()), e.GetCommon().name, e.GetCommon().Type) |
| } |
| |
| // xmlName returns the xml name of the element or attribute |
| func xmlName(f reflect.StructField) (name string, attr bool) { |
| tags := strings.Split(f.Tag.Get("xml"), ",") |
| for _, s := range tags { |
| attr = attr || s == "attr" |
| } |
| return tags[0], attr |
| } |
| |
| func findField(v reflect.Value, key string) (reflect.Value, error) { |
| v = reflect.Indirect(v) |
| for i := iter(v); !i.done(); i.next() { |
| if n, _ := xmlName(i.field()); n == key { |
| return i.value(), nil |
| } |
| } |
| return reflect.Value{}, fmt.Errorf("cldr: no field %q in element %#v", key, v.Interface()) |
| } |
| |
| var xpathPart = regexp.MustCompile(`(\pL+)(?:\[@(\pL+)='([\w-]+)'\])?`) |
| |
| func walkXPath(e Elem, path string) (res Elem, err error) { |
| for _, c := range strings.Split(path, "/") { |
| if c == ".." { |
| if e = e.enclosing(); e == nil { |
| panic("path ..") |
| return nil, fmt.Errorf(`cldr: ".." moves past root in path %q`, path) |
| } |
| continue |
| } else if c == "" { |
| continue |
| } |
| m := xpathPart.FindStringSubmatch(c) |
| if len(m) == 0 || len(m[0]) != len(c) { |
| return nil, fmt.Errorf("cldr: syntax error in path component %q", c) |
| } |
| v, err := findField(reflect.ValueOf(e), m[1]) |
| if err != nil { |
| return nil, err |
| } |
| switch v.Kind() { |
| case reflect.Slice: |
| i := 0 |
| if m[2] != "" || v.Len() > 1 { |
| if m[2] == "" { |
| m[2] = "type" |
| if m[3] = e.GetCommon().Default(); m[3] == "" { |
| return nil, fmt.Errorf("cldr: type selector or default value needed for element %s", m[1]) |
| } |
| } |
| for ; i < v.Len(); i++ { |
| vi := v.Index(i) |
| key, err := findField(vi.Elem(), m[2]) |
| if err != nil { |
| return nil, err |
| } |
| key = reflect.Indirect(key) |
| if key.Kind() == reflect.String && key.String() == m[3] { |
| break |
| } |
| } |
| } |
| if i == v.Len() || v.Index(i).IsNil() { |
| return nil, fmt.Errorf("no %s found with %s==%s", m[1], m[2], m[3]) |
| } |
| e = v.Index(i).Interface().(Elem) |
| case reflect.Ptr: |
| if v.IsNil() { |
| return nil, fmt.Errorf("cldr: element %q not found within element %q", m[1], e.GetCommon().name) |
| } |
| var ok bool |
| if e, ok = v.Interface().(Elem); !ok { |
| return nil, fmt.Errorf("cldr: %q is not an XML element", m[1]) |
| } else if m[2] != "" || m[3] != "" { |
| return nil, fmt.Errorf("cldr: no type selector allowed for element %s", m[1]) |
| } |
| default: |
| return nil, fmt.Errorf("cldr: %q is not an XML element", m[1]) |
| } |
| } |
| return e, nil |
| } |
| |
| const absPrefix = "//ldml/" |
| |
| func (cldr *CLDR) resolveAlias(e Elem, src, path string) (res Elem, err error) { |
| if src != "locale" { |
| if !strings.HasPrefix(path, absPrefix) { |
| return nil, fmt.Errorf("cldr: expected absolute path, found %q", path) |
| } |
| path = path[len(absPrefix):] |
| if e, err = cldr.resolve(src); err != nil { |
| return nil, err |
| } |
| } |
| return walkXPath(e, path) |
| } |
| |
| func (cldr *CLDR) resolveAndMergeAlias(e Elem) error { |
| alias := e.GetCommon().Alias |
| if alias == nil { |
| return nil |
| } |
| a, err := cldr.resolveAlias(e, alias.Source, alias.Path) |
| if err != nil { |
| return fmt.Errorf("%v: error evaluating path %q: %v", getPath(e), alias.Path, err) |
| } |
| // Ensure alias node was already evaluated. TODO: avoid double evaluation. |
| err = cldr.resolveAndMergeAlias(a) |
| v := reflect.ValueOf(e).Elem() |
| for i := iter(reflect.ValueOf(a).Elem()); !i.done(); i.next() { |
| if vv := i.value(); vv.Kind() != reflect.Ptr || !vv.IsNil() { |
| if _, attr := xmlName(i.field()); !attr { |
| v.FieldByIndex(i.index).Set(vv) |
| } |
| } |
| } |
| return err |
| } |
| |
| func (cldr *CLDR) aliasResolver() visitor { |
| return func(v reflect.Value) (err error) { |
| if e, ok := v.Addr().Interface().(Elem); ok { |
| err = cldr.resolveAndMergeAlias(e) |
| if err == nil && blocking[e.GetCommon().name] { |
| return stopDescent |
| } |
| } |
| return err |
| } |
| } |
| |
| // elements within blocking elements do not inherit. |
| // Taken from CLDR's supplementalMetaData.xml. |
| var blocking = map[string]bool{ |
| "identity": true, |
| "supplementalData": true, |
| "cldrTest": true, |
| "collation": true, |
| "transform": true, |
| } |
| |
| // Distinguishing attributes affect inheritance; two elements with different |
| // distinguishing attributes are treated as different for purposes of inheritance, |
| // except when such attributes occur in the indicated elements. |
| // Taken from CLDR's supplementalMetaData.xml. |
| var distinguishing = map[string][]string{ |
| "key": nil, |
| "request_id": nil, |
| "id": nil, |
| "registry": nil, |
| "alt": nil, |
| "iso4217": nil, |
| "iso3166": nil, |
| "mzone": nil, |
| "from": nil, |
| "to": nil, |
| "type": []string{ |
| "abbreviationFallback", |
| "default", |
| "mapping", |
| "measurementSystem", |
| "preferenceOrdering", |
| }, |
| "numberSystem": nil, |
| } |
| |
| func in(set []string, s string) bool { |
| for _, v := range set { |
| if v == s { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // attrKey computes a key based on the distinguishable attributes of |
| // an element and its values. |
| func attrKey(v reflect.Value, exclude ...string) string { |
| parts := []string{} |
| ename := v.Interface().(Elem).GetCommon().name |
| v = v.Elem() |
| for i := iter(v); !i.done(); i.next() { |
| if name, attr := xmlName(i.field()); attr { |
| if except, ok := distinguishing[name]; ok && !in(exclude, name) && !in(except, ename) { |
| v := i.value() |
| if v.Kind() == reflect.Ptr { |
| v = v.Elem() |
| } |
| if v.IsValid() { |
| parts = append(parts, fmt.Sprintf("%s=%s", name, v.String())) |
| } |
| } |
| } |
| } |
| sort.Strings(parts) |
| return strings.Join(parts, ";") |
| } |
| |
| // Key returns a key for e derived from all distinguishing attributes |
| // except those specified by exclude. |
| func Key(e Elem, exclude ...string) string { |
| return attrKey(reflect.ValueOf(e), exclude...) |
| } |
| |
| // linkEnclosing sets the enclosing element as well as the name |
| // for all sub-elements of child, recursively. |
| func linkEnclosing(parent, child Elem) { |
| child.setEnclosing(parent) |
| v := reflect.ValueOf(child).Elem() |
| for i := iter(v); !i.done(); i.next() { |
| vf := i.value() |
| if vf.Kind() == reflect.Slice { |
| for j := 0; j < vf.Len(); j++ { |
| linkEnclosing(child, vf.Index(j).Interface().(Elem)) |
| } |
| } else if vf.Kind() == reflect.Ptr && !vf.IsNil() && vf.Elem().Kind() == reflect.Struct { |
| linkEnclosing(child, vf.Interface().(Elem)) |
| } |
| } |
| } |
| |
| func setNames(e Elem, name string) { |
| e.setName(name) |
| v := reflect.ValueOf(e).Elem() |
| for i := iter(v); !i.done(); i.next() { |
| vf := i.value() |
| name, _ = xmlName(i.field()) |
| if vf.Kind() == reflect.Slice { |
| for j := 0; j < vf.Len(); j++ { |
| setNames(vf.Index(j).Interface().(Elem), name) |
| } |
| } else if vf.Kind() == reflect.Ptr && !vf.IsNil() && vf.Elem().Kind() == reflect.Struct { |
| setNames(vf.Interface().(Elem), name) |
| } |
| } |
| } |
| |
| // deepCopy copies elements of v recursively. All elements of v that may |
| // be modified by inheritance are explicitly copied. |
| func deepCopy(v reflect.Value) reflect.Value { |
| switch v.Kind() { |
| case reflect.Ptr: |
| if v.IsNil() || v.Elem().Kind() != reflect.Struct { |
| return v |
| } |
| nv := reflect.New(v.Elem().Type()) |
| nv.Elem().Set(v.Elem()) |
| deepCopyRec(nv.Elem(), v.Elem()) |
| return nv |
| case reflect.Slice: |
| nv := reflect.MakeSlice(v.Type(), v.Len(), v.Len()) |
| for i := 0; i < v.Len(); i++ { |
| deepCopyRec(nv.Index(i), v.Index(i)) |
| } |
| return nv |
| } |
| panic("deepCopy: must be called with pointer or slice") |
| } |
| |
| // deepCopyRec is only called by deepCopy. |
| func deepCopyRec(nv, v reflect.Value) { |
| if v.Kind() == reflect.Struct { |
| t := v.Type() |
| for i := 0; i < v.NumField(); i++ { |
| if name, attr := xmlName(t.Field(i)); name != "" && !attr { |
| deepCopyRec(nv.Field(i), v.Field(i)) |
| } |
| } |
| } else { |
| nv.Set(deepCopy(v)) |
| } |
| } |
| |
| // newNode is used to insert a missing node during inheritance. |
| func (cldr *CLDR) newNode(v, enc reflect.Value) reflect.Value { |
| n := reflect.New(v.Type()) |
| for i := iter(v); !i.done(); i.next() { |
| if name, attr := xmlName(i.field()); name == "" || attr { |
| n.Elem().FieldByIndex(i.index).Set(i.value()) |
| } |
| } |
| n.Interface().(Elem).GetCommon().setEnclosing(enc.Addr().Interface().(Elem)) |
| return n |
| } |
| |
| // v, parent must be pointers to struct |
| func (cldr *CLDR) inheritFields(v, parent reflect.Value) (res reflect.Value, err error) { |
| t := v.Type() |
| nv := reflect.New(t) |
| nv.Elem().Set(v) |
| for i := iter(v); !i.done(); i.next() { |
| vf := i.value() |
| f := i.field() |
| name, attr := xmlName(f) |
| if name == "" || attr { |
| continue |
| } |
| pf := parent.FieldByIndex(i.index) |
| if blocking[name] { |
| if vf.IsNil() { |
| vf = pf |
| } |
| nv.Elem().FieldByIndex(i.index).Set(deepCopy(vf)) |
| continue |
| } |
| switch f.Type.Kind() { |
| case reflect.Ptr: |
| if f.Type.Elem().Kind() == reflect.Struct { |
| if !vf.IsNil() { |
| if vf, err = cldr.inheritStructPtr(vf, pf); err != nil { |
| return reflect.Value{}, err |
| } |
| vf.Interface().(Elem).setEnclosing(nv.Interface().(Elem)) |
| nv.Elem().FieldByIndex(i.index).Set(vf) |
| } else if !pf.IsNil() { |
| n := cldr.newNode(pf.Elem(), v) |
| if vf, err = cldr.inheritStructPtr(n, pf); err != nil { |
| return reflect.Value{}, err |
| } |
| vf.Interface().(Elem).setEnclosing(nv.Interface().(Elem)) |
| nv.Elem().FieldByIndex(i.index).Set(vf) |
| } |
| } |
| case reflect.Slice: |
| vf, err := cldr.inheritSlice(nv.Elem(), vf, pf) |
| if err != nil { |
| return reflect.Zero(t), err |
| } |
| nv.Elem().FieldByIndex(i.index).Set(vf) |
| } |
| } |
| return nv, nil |
| } |
| |
| func root(e Elem) *LDML { |
| for ; e.enclosing() != nil; e = e.enclosing() { |
| } |
| return e.(*LDML) |
| } |
| |
| // inheritStructPtr first merges possible aliases in with v and then inherits |
| // any underspecified elements from parent. |
| func (cldr *CLDR) inheritStructPtr(v, parent reflect.Value) (r reflect.Value, err error) { |
| if !v.IsNil() { |
| e := v.Interface().(Elem).GetCommon() |
| alias := e.Alias |
| if alias == nil && !parent.IsNil() { |
| alias = parent.Interface().(Elem).GetCommon().Alias |
| } |
| if alias != nil { |
| a, err := cldr.resolveAlias(v.Interface().(Elem), alias.Source, alias.Path) |
| if a != nil { |
| if v, err = cldr.inheritFields(v.Elem(), reflect.ValueOf(a).Elem()); err != nil { |
| return reflect.Value{}, err |
| } |
| } |
| } |
| if !parent.IsNil() { |
| return cldr.inheritFields(v.Elem(), parent.Elem()) |
| } |
| } else if parent.IsNil() { |
| panic("should not reach here") |
| } |
| return v, nil |
| } |
| |
| // Must be slice of struct pointers. |
| func (cldr *CLDR) inheritSlice(enc, v, parent reflect.Value) (res reflect.Value, err error) { |
| t := v.Type() |
| index := make(map[string]reflect.Value) |
| if !v.IsNil() { |
| for i := 0; i < v.Len(); i++ { |
| vi := v.Index(i) |
| key := attrKey(vi) |
| index[key] = vi |
| } |
| } |
| if !parent.IsNil() { |
| for i := 0; i < parent.Len(); i++ { |
| vi := parent.Index(i) |
| key := attrKey(vi) |
| if w, ok := index[key]; ok { |
| index[key], err = cldr.inheritStructPtr(w, vi) |
| } else { |
| n := cldr.newNode(vi.Elem(), enc) |
| index[key], err = cldr.inheritStructPtr(n, vi) |
| } |
| index[key].Interface().(Elem).setEnclosing(enc.Addr().Interface().(Elem)) |
| if err != nil { |
| return v, err |
| } |
| } |
| } |
| keys := make([]string, 0, len(index)) |
| for k, _ := range index { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| sl := reflect.MakeSlice(t, len(index), len(index)) |
| for i, k := range keys { |
| sl.Index(i).Set(index[k]) |
| } |
| return sl, nil |
| } |
| |
| func parentLocale(loc string) string { |
| parts := strings.Split(loc, "_") |
| if len(parts) == 1 { |
| return "root" |
| } |
| parts = parts[:len(parts)-1] |
| key := strings.Join(parts, "_") |
| return key |
| } |
| |
| func (cldr *CLDR) resolve(loc string) (res *LDML, err error) { |
| if r := cldr.resolved[loc]; r != nil { |
| return r, nil |
| } |
| x := cldr.RawLDML(loc) |
| if x == nil { |
| return nil, fmt.Errorf("cldr: unknown locale %q", loc) |
| } |
| var v reflect.Value |
| if loc == "root" { |
| x = deepCopy(reflect.ValueOf(x)).Interface().(*LDML) |
| linkEnclosing(nil, x) |
| err = cldr.aliasResolver().visit(x) |
| } else { |
| key := parentLocale(loc) |
| var parent *LDML |
| for ; cldr.locale[key] == nil; key = parentLocale(key) { |
| } |
| if parent, err = cldr.resolve(key); err != nil { |
| return nil, err |
| } |
| v, err = cldr.inheritFields(reflect.ValueOf(x).Elem(), reflect.ValueOf(parent).Elem()) |
| x = v.Interface().(*LDML) |
| linkEnclosing(nil, x) |
| } |
| if err != nil { |
| return nil, err |
| } |
| cldr.resolved[loc] = x |
| return x, err |
| } |
| |
| // finalize finalizes the initialization of the raw LDML structs. It also |
| // removed unwanted fields, as specified by filter, so that they will not |
| // be unnecessarily evaluated. |
| func (cldr *CLDR) finalize(filter []string) { |
| for _, x := range cldr.locale { |
| if filter != nil { |
| v := reflect.ValueOf(x).Elem() |
| t := v.Type() |
| for i := 0; i < v.NumField(); i++ { |
| f := t.Field(i) |
| name, _ := xmlName(f) |
| if name != "" && name != "identity" && !in(filter, name) { |
| v.Field(i).Set(reflect.Zero(f.Type)) |
| } |
| } |
| } |
| linkEnclosing(nil, x) // for resolving aliases and paths |
| setNames(x, "ldml") |
| } |
| } |