602 lines
15 KiB
Go
602 lines
15 KiB
Go
// 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")
|
|
}
|
|
}
|