1// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. 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 po
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"strconv"
12	"strings"
13)
14
15// A PO file is made up of many entries,
16// each entry holding the relation between an original untranslated string
17// and its corresponding translation.
18//
19// See http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
20type Message struct {
21	Comment               // Coments
22	MsgContext   string   // msgctxt context
23	MsgId        string   // msgid untranslated-string
24	MsgIdPlural  string   // msgid_plural untranslated-string-plural
25	MsgStr       string   // msgstr translated-string
26	MsgStrPlural []string // msgstr[0] translated-string-case-0
27}
28
29type byMessages []Message
30
31func (d byMessages) Len() int {
32	return len(d)
33}
34func (d byMessages) Less(i, j int) bool {
35	if d[i].Comment.less(&d[j].Comment) {
36		return true
37	}
38	if a, b := d[i].MsgContext, d[j].MsgContext; a != b {
39		return a < b
40	}
41	if a, b := d[i].MsgId, d[j].MsgId; a != b {
42		return a < b
43	}
44	if a, b := d[i].MsgIdPlural, d[j].MsgIdPlural; a != b {
45		return a < b
46	}
47	return false
48}
49func (d byMessages) Swap(i, j int) {
50	d[i], d[j] = d[j], d[i]
51}
52
53func (p *Message) readPoEntry(r *lineReader) (err error) {
54	*p = Message{}
55	if err = r.skipBlankLine(); err != nil {
56		return
57	}
58	defer func(oldPos int) {
59		newPos := r.currentPos()
60		if newPos != oldPos && err == io.EOF {
61			err = nil
62		}
63	}(r.currentPos())
64
65	if err = p.Comment.readPoComment(r); err != nil {
66		return
67	}
68	for {
69		var s string
70		if s, _, err = r.currentLine(); err != nil {
71			return
72		}
73
74		if p.isInvalidLine(s) {
75			err = fmt.Errorf("gettext: line %d, %v", r.currentPos(), "invalid line")
76			return
77		}
78		if reComment.MatchString(s) || reBlankLine.MatchString(s) {
79			return
80		}
81
82		if err = p.readMsgContext(r); err != nil {
83			return
84		}
85		if err = p.readMsgId(r); err != nil {
86			return
87		}
88		if err = p.readMsgIdPlural(r); err != nil {
89			return
90		}
91		if err = p.readMsgStrOrPlural(r); err != nil {
92			return
93		}
94	}
95}
96
97func (p *Message) readMsgContext(r *lineReader) (err error) {
98	var s string
99	if s, _, err = r.currentLine(); err != nil {
100		return
101	}
102	if !reMsgContext.MatchString(s) {
103		return
104	}
105	p.MsgContext, err = p.readString(r)
106	return
107}
108
109func (p *Message) readMsgId(r *lineReader) (err error) {
110	var s string
111	if s, _, err = r.currentLine(); err != nil {
112		return
113	}
114	if !reMsgId.MatchString(s) {
115		return
116	}
117	p.MsgId, err = p.readString(r)
118	return
119}
120
121func (p *Message) readMsgIdPlural(r *lineReader) (err error) {
122	var s string
123	if s, _, err = r.currentLine(); err != nil {
124		return
125	}
126	if !reMsgIdPlural.MatchString(s) {
127		return
128	}
129	p.MsgIdPlural, err = p.readString(r)
130	return nil
131}
132
133func (p *Message) readMsgStrOrPlural(r *lineReader) (err error) {
134	var s string
135	if s, _, err = r.currentLine(); err != nil {
136		return
137	}
138	if !reMsgStr.MatchString(s) && !reMsgStrPlural.MatchString(s) {
139		return
140	}
141	if reMsgStrPlural.MatchString(s) {
142		left, right := strings.Index(s, `[`), strings.LastIndex(s, `]`)
143		idx, _ := strconv.Atoi(s[left+1 : right])
144		s, err = p.readString(r)
145		if n := len(p.MsgStrPlural); (idx + 1) > n {
146			p.MsgStrPlural = append(p.MsgStrPlural, make([]string, (idx+1)-n)...)
147		}
148		p.MsgStrPlural[idx] = s
149	} else {
150		p.MsgStr, err = p.readString(r)
151	}
152	return nil
153}
154
155func (p *Message) readString(r *lineReader) (msg string, err error) {
156	var s string
157	if s, _, err = r.readLine(); err != nil {
158		return
159	}
160	msg += decodePoString(s)
161	for {
162		if s, _, err = r.readLine(); err != nil {
163			return
164		}
165		if !reStringLine.MatchString(s) {
166			r.unreadLine()
167			break
168		}
169		msg += decodePoString(s)
170	}
171	return
172}
173
174// String returns the po format entry string.
175func (p Message) String() string {
176	var buf bytes.Buffer
177	fmt.Fprintf(&buf, "%s", p.Comment.String())
178	fmt.Fprintf(&buf, "msgid %s", encodePoString(p.MsgId))
179	if p.MsgIdPlural != "" {
180		fmt.Fprintf(&buf, "msgid_plural %s", encodePoString(p.MsgIdPlural))
181	}
182	if p.MsgStr != "" {
183		fmt.Fprintf(&buf, "msgstr %s", encodePoString(p.MsgStr))
184	}
185	for i := 0; i < len(p.MsgStrPlural); i++ {
186		fmt.Fprintf(&buf, "msgstr[%d] %s", i, encodePoString(p.MsgStrPlural[i]))
187	}
188	return buf.String()
189}
190