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.Reader
62			if r, err = l.Reader(i); err == nil {
63				err = d.decode(m[1], m[2], r)
64			}
65			if err != nil {
66				return nil, err
67			}
68		}
69	}
70	d.cldr.finalize(d.sectionFilter)
71	return d.cldr, nil
72}
73
74func (d *Decoder) decode(dir, id string, r io.Reader) error {
75	var v interface{}
76	var l *LDML
77	cldr := d.cldr
78	switch {
79	case dir == "supplemental":
80		v = cldr.supp
81	case dir == "transforms":
82		return nil
83	case dir == "bcp47":
84		v = cldr.bcp47
85	case dir == "validity":
86		return nil
87	default:
88		ok := false
89		if v, ok = cldr.locale[id]; !ok {
90			l = &LDML{}
91			v, cldr.locale[id] = l, l
92		}
93	}
94	x := xml.NewDecoder(r)
95	if err := x.Decode(v); err != nil {
96		log.Printf("%s/%s: %v", dir, id, err)
97		return err
98	}
99	if l != nil {
100		if l.Identity == nil {
101			return fmt.Errorf("%s/%s: missing identity element", dir, id)
102		}
103		// TODO: verify when CLDR bug http://unicode.org/cldr/trac/ticket/8970
104		// is resolved.
105		// path := strings.Split(id, "_")
106		// if lang := l.Identity.Language.Type; lang != path[0] {
107		// 	return fmt.Errorf("%s/%s: language was %s; want %s", dir, id, lang, path[0])
108		// }
109	}
110	return nil
111}
112
113type pathLoader []string
114
115func makePathLoader(path string) (pl pathLoader, err error) {
116	err = filepath.Walk(path, func(path string, _ os.FileInfo, err error) error {
117		pl = append(pl, path)
118		return err
119	})
120	return pl, err
121}
122
123func (pl pathLoader) Len() int {
124	return len(pl)
125}
126
127func (pl pathLoader) Path(i int) string {
128	return pl[i]
129}
130
131func (pl pathLoader) Reader(i int) (io.ReadCloser, error) {
132	return os.Open(pl[i])
133}
134
135// DecodePath loads CLDR data from the given path.
136func (d *Decoder) DecodePath(path string) (cldr *CLDR, err error) {
137	loader, err := makePathLoader(path)
138	if err != nil {
139		return nil, err
140	}
141	return d.Decode(loader)
142}
143
144type zipLoader struct {
145	r *zip.Reader
146}
147
148func (zl zipLoader) Len() int {
149	return len(zl.r.File)
150}
151
152func (zl zipLoader) Path(i int) string {
153	return zl.r.File[i].Name
154}
155
156func (zl zipLoader) Reader(i int) (io.ReadCloser, error) {
157	return zl.r.File[i].Open()
158}
159
160// DecodeZip loads CLDR data from the zip archive for which r is the source.
161func (d *Decoder) DecodeZip(r io.Reader) (cldr *CLDR, err error) {
162	buffer, err := ioutil.ReadAll(r)
163	if err != nil {
164		return nil, err
165	}
166	archive, err := zip.NewReader(bytes.NewReader(buffer), int64(len(buffer)))
167	if err != nil {
168		return nil, err
169	}
170	return d.Decode(zipLoader{archive})
171}
172