| // 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 specied 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}) |
| } |