blob: 48f6bd629f924b68739b91997d7c2c9177547b7c [file] [log] [blame]
Don Newton98fd8812019-09-23 15:15:02 -04001// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package cldr
6
7import (
8 "archive/zip"
9 "bytes"
10 "encoding/xml"
11 "fmt"
12 "io"
13 "io/ioutil"
14 "log"
15 "os"
16 "path/filepath"
17 "regexp"
18)
19
20// A Decoder loads an archive of CLDR data.
21type Decoder struct {
22 dirFilter []string
23 sectionFilter []string
24 loader Loader
25 cldr *CLDR
26 curLocale string
27}
28
29// SetSectionFilter takes a list top-level LDML element names to which
30// evaluation of LDML should be limited. It automatically calls SetDirFilter.
31func (d *Decoder) SetSectionFilter(filter ...string) {
32 d.sectionFilter = filter
33 // TODO: automatically set dir filter
34}
35
36// SetDirFilter limits the loading of LDML XML files of the specied directories.
37// Note that sections may be split across directories differently for different CLDR versions.
38// For more robust code, use SetSectionFilter.
39func (d *Decoder) SetDirFilter(dir ...string) {
40 d.dirFilter = dir
41}
42
43// A Loader provides access to the files of a CLDR archive.
44type Loader interface {
45 Len() int
46 Path(i int) string
47 Reader(i int) (io.ReadCloser, error)
48}
49
50var fileRe = regexp.MustCompile(`.*[/\\](.*)[/\\](.*)\.xml`)
51
52// Decode loads and decodes the files represented by l.
53func (d *Decoder) Decode(l Loader) (cldr *CLDR, err error) {
54 d.cldr = makeCLDR()
55 for i := 0; i < l.Len(); i++ {
56 fname := l.Path(i)
57 if m := fileRe.FindStringSubmatch(fname); m != nil {
58 if len(d.dirFilter) > 0 && !in(d.dirFilter, m[1]) {
59 continue
60 }
61 var r io.ReadCloser
62 if r, err = l.Reader(i); err == nil {
63 err = d.decode(m[1], m[2], r)
64 r.Close()
65 }
66 if err != nil {
67 return nil, err
68 }
69 }
70 }
71 d.cldr.finalize(d.sectionFilter)
72 return d.cldr, nil
73}
74
75func (d *Decoder) decode(dir, id string, r io.Reader) error {
76 var v interface{}
77 var l *LDML
78 cldr := d.cldr
79 switch {
80 case dir == "supplemental":
81 v = cldr.supp
82 case dir == "transforms":
83 return nil
84 case dir == "bcp47":
85 v = cldr.bcp47
86 case dir == "validity":
87 return nil
88 default:
89 ok := false
90 if v, ok = cldr.locale[id]; !ok {
91 l = &LDML{}
92 v, cldr.locale[id] = l, l
93 }
94 }
95 x := xml.NewDecoder(r)
96 if err := x.Decode(v); err != nil {
97 log.Printf("%s/%s: %v", dir, id, err)
98 return err
99 }
100 if l != nil {
101 if l.Identity == nil {
102 return fmt.Errorf("%s/%s: missing identity element", dir, id)
103 }
104 // TODO: verify when CLDR bug https://unicode.org/cldr/trac/ticket/8970
105 // is resolved.
106 // path := strings.Split(id, "_")
107 // if lang := l.Identity.Language.Type; lang != path[0] {
108 // return fmt.Errorf("%s/%s: language was %s; want %s", dir, id, lang, path[0])
109 // }
110 }
111 return nil
112}
113
114type pathLoader []string
115
116func makePathLoader(path string) (pl pathLoader, err error) {
117 err = filepath.Walk(path, func(path string, _ os.FileInfo, err error) error {
118 pl = append(pl, path)
119 return err
120 })
121 return pl, err
122}
123
124func (pl pathLoader) Len() int {
125 return len(pl)
126}
127
128func (pl pathLoader) Path(i int) string {
129 return pl[i]
130}
131
132func (pl pathLoader) Reader(i int) (io.ReadCloser, error) {
133 return os.Open(pl[i])
134}
135
136// DecodePath loads CLDR data from the given path.
137func (d *Decoder) DecodePath(path string) (cldr *CLDR, err error) {
138 loader, err := makePathLoader(path)
139 if err != nil {
140 return nil, err
141 }
142 return d.Decode(loader)
143}
144
145type zipLoader struct {
146 r *zip.Reader
147}
148
149func (zl zipLoader) Len() int {
150 return len(zl.r.File)
151}
152
153func (zl zipLoader) Path(i int) string {
154 return zl.r.File[i].Name
155}
156
157func (zl zipLoader) Reader(i int) (io.ReadCloser, error) {
158 return zl.r.File[i].Open()
159}
160
161// DecodeZip loads CLDR data from the zip archive for which r is the source.
162func (d *Decoder) DecodeZip(r io.Reader) (cldr *CLDR, err error) {
163 buffer, err := ioutil.ReadAll(r)
164 if err != nil {
165 return nil, err
166 }
167 archive, err := zip.NewReader(bytes.NewReader(buffer), int64(len(buffer)))
168 if err != nil {
169 return nil, err
170 }
171 return d.Decode(zipLoader{archive})
172}