1// 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}
173