| // 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 | |
| import ( | |
| "archive/zip" | |
| "bytes" | |
| "encoding/xml" | |
| "fmt" | |
| "io" | |
| "log" | |
| "os" | |
| "path/filepath" | |
| "regexp" | |
| ) | |
| // A Decoder loads an archive of CLDR data. | |
| type Decoder struct { | |
| dirFilter []string | |
| sectionFilter []string | |
| loader Loader | |
| cldr *CLDR | |
| curLocale string | |
| } | |
| // SetSectionFilter takes a list top-level LDML element names to which | |
| // evaluation of LDML should be limited. It automatically calls SetDirFilter. | |
| func (d *Decoder) SetSectionFilter(filter ...string) { | |
| d.sectionFilter = filter | |
| // TODO: automatically set dir filter | |
| } | |
| // SetDirFilter limits the loading of LDML XML files of the specified directories. | |
| // Note that sections may be split across directories differently for different CLDR versions. | |
| // For more robust code, use SetSectionFilter. | |
| func (d *Decoder) SetDirFilter(dir ...string) { | |
| d.dirFilter = dir | |
| } | |
| // A Loader provides access to the files of a CLDR archive. | |
| type Loader interface { | |
| Len() int | |
| Path(i int) string | |
| Reader(i int) (io.ReadCloser, error) | |
| } | |
| var fileRe = regexp.MustCompile(`.*[/\\](.*)[/\\](.*)\.xml`) | |
| // Decode loads and decodes the files represented by l. | |
| func (d *Decoder) Decode(l Loader) (cldr *CLDR, err error) { | |
| d.cldr = makeCLDR() | |
| for i := 0; i < l.Len(); i++ { | |
| fname := l.Path(i) | |
| if m := fileRe.FindStringSubmatch(fname); m != nil { | |
| if len(d.dirFilter) > 0 && !in(d.dirFilter, m[1]) { | |
| continue | |
| } | |
| var r io.ReadCloser | |
| if r, err = l.Reader(i); err == nil { | |
| err = d.decode(m[1], m[2], r) | |
| r.Close() | |
| } | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| d.cldr.finalize(d.sectionFilter) | |
| return d.cldr, nil | |
| } | |
| func (d *Decoder) decode(dir, id string, r io.Reader) error { | |
| var v interface{} | |
| var l *LDML | |
| cldr := d.cldr | |
| switch { | |
| case dir == "supplemental": | |
| v = cldr.supp | |
| case dir == "transforms": | |
| return nil | |
| case dir == "bcp47": | |
| v = cldr.bcp47 | |
| case dir == "validity": | |
| return nil | |
| default: | |
| ok := false | |
| if v, ok = cldr.locale[id]; !ok { | |
| l = &LDML{} | |
| v, cldr.locale[id] = l, l | |
| } | |
| } | |
| x := xml.NewDecoder(r) | |
| if err := x.Decode(v); err != nil { | |
| log.Printf("%s/%s: %v", dir, id, err) | |
| return err | |
| } | |
| if l != nil { | |
| if l.Identity == nil { | |
| return fmt.Errorf("%s/%s: missing identity element", dir, id) | |
| } | |
| // TODO: verify when CLDR bug https://unicode.org/cldr/trac/ticket/8970 | |
| // is resolved. | |
| // path := strings.Split(id, "_") | |
| // if lang := l.Identity.Language.Type; lang != path[0] { | |
| // return fmt.Errorf("%s/%s: language was %s; want %s", dir, id, lang, path[0]) | |
| // } | |
| } | |
| return nil | |
| } | |
| type pathLoader []string | |
| func makePathLoader(path string) (pl pathLoader, err error) { | |
| err = filepath.Walk(path, func(path string, _ os.FileInfo, err error) error { | |
| pl = append(pl, path) | |
| return err | |
| }) | |
| return pl, err | |
| } | |
| func (pl pathLoader) Len() int { | |
| return len(pl) | |
| } | |
| func (pl pathLoader) Path(i int) string { | |
| return pl[i] | |
| } | |
| func (pl pathLoader) Reader(i int) (io.ReadCloser, error) { | |
| return os.Open(pl[i]) | |
| } | |
| // DecodePath loads CLDR data from the given path. | |
| func (d *Decoder) DecodePath(path string) (cldr *CLDR, err error) { | |
| loader, err := makePathLoader(path) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return d.Decode(loader) | |
| } | |
| type zipLoader struct { | |
| r *zip.Reader | |
| } | |
| func (zl zipLoader) Len() int { | |
| return len(zl.r.File) | |
| } | |
| func (zl zipLoader) Path(i int) string { | |
| return zl.r.File[i].Name | |
| } | |
| func (zl zipLoader) Reader(i int) (io.ReadCloser, error) { | |
| return zl.r.File[i].Open() | |
| } | |
| // DecodeZip loads CLDR data from the zip archive for which r is the source. | |
| func (d *Decoder) DecodeZip(r io.Reader) (cldr *CLDR, err error) { | |
| buffer, err := io.ReadAll(r) | |
| if err != nil { | |
| return nil, err | |
| } | |
| archive, err := zip.NewReader(bytes.NewReader(buffer), int64(len(buffer))) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return d.Decode(zipLoader{archive}) | |
| } |