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