You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
602 lines
15 KiB
602 lines
15 KiB
// 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) { |
|
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") |
|
} |
|
}
|
|
|