Index: locale/cldr/resolve.go |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/locale/cldr/resolve.go |
@@ -0,0 +1,603 @@ |
+// 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 http://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 it's 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) { |
+ e := v.Interface().(Elem).GetCommon() |
+ p := parent.Interface().(Elem).GetCommon() |
+ if e != nil { |
+ alias := e.Alias |
+ if alias == nil && p != nil { |
+ alias = p.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 p != nil { |
+ return cldr.inheritFields(v.Elem(), parent.Elem()) |
+ } |
+ } else if p != nil { |
+ 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") |
+ } |
+} |