1
2
3
4
5 package mo
6
7 import (
8 "bytes"
9 "encoding/binary"
10 "fmt"
11 "io/ioutil"
12 "strings"
13 )
14
15 const (
16 MoHeaderSize = 28
17 MoMagicLittleEndian = 0x950412de
18 MoMagicBigEndian = 0xde120495
19
20 EotSeparator = "\x04"
21 NulSeparator = "\x00"
22 )
23
24
25
26
27 type File struct {
28 MagicNumber uint32
29 MajorVersion uint16
30 MinorVersion uint16
31 MsgIdCount uint32
32 MsgIdOffset uint32
33 MsgStrOffset uint32
34 HashSize uint32
35 HashOffset uint32
36 MimeHeader Header
37 Messages []Message
38 }
39
40
41 func Load(data []byte) (*File, error) {
42 return loadData(data)
43 }
44
45
46 func LoadFile(path string) (*File, error) {
47 data, err := ioutil.ReadFile(path)
48 if err != nil {
49 return nil, err
50 }
51 return loadData(data)
52 }
53
54 func loadData(data []byte) (*File, error) {
55 r := bytes.NewReader(data)
56
57 var magicNumber uint32
58 if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
59 return nil, fmt.Errorf("gettext: %v", err)
60 }
61 var bo binary.ByteOrder
62 switch magicNumber {
63 case MoMagicLittleEndian:
64 bo = binary.LittleEndian
65 case MoMagicBigEndian:
66 bo = binary.BigEndian
67 default:
68 return nil, fmt.Errorf("gettext: %v", "invalid magic number")
69 }
70
71 var header struct {
72 MajorVersion uint16
73 MinorVersion uint16
74 MsgIdCount uint32
75 MsgIdOffset uint32
76 MsgStrOffset uint32
77 HashSize uint32
78 HashOffset uint32
79 }
80 if err := binary.Read(r, bo, &header); err != nil {
81 return nil, fmt.Errorf("gettext: %v", err)
82 }
83 if v := header.MajorVersion; v != 0 && v != 1 {
84 return nil, fmt.Errorf("gettext: %v", "invalid version number")
85 }
86 if v := header.MinorVersion; v != 0 && v != 1 {
87 return nil, fmt.Errorf("gettext: %v", "invalid version number")
88 }
89
90 msgIdStart := make([]uint32, header.MsgIdCount)
91 msgIdLen := make([]uint32, header.MsgIdCount)
92 if _, err := r.Seek(int64(header.MsgIdOffset), 0); err != nil {
93 return nil, fmt.Errorf("gettext: %v", err)
94 }
95 for i := 0; i < int(header.MsgIdCount); i++ {
96 if err := binary.Read(r, bo, &msgIdLen[i]); err != nil {
97 return nil, fmt.Errorf("gettext: %v", err)
98 }
99 if err := binary.Read(r, bo, &msgIdStart[i]); err != nil {
100 return nil, fmt.Errorf("gettext: %v", err)
101 }
102 }
103
104 msgStrStart := make([]int32, header.MsgIdCount)
105 msgStrLen := make([]int32, header.MsgIdCount)
106 if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
107 return nil, fmt.Errorf("gettext: %v", err)
108 }
109 for i := 0; i < int(header.MsgIdCount); i++ {
110 if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
111 return nil, fmt.Errorf("gettext: %v", err)
112 }
113 if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
114 return nil, fmt.Errorf("gettext: %v", err)
115 }
116 }
117
118 file := &File{
119 MagicNumber: magicNumber,
120 MajorVersion: header.MajorVersion,
121 MinorVersion: header.MinorVersion,
122 MsgIdCount: header.MsgIdCount,
123 MsgIdOffset: header.MsgIdOffset,
124 MsgStrOffset: header.MsgStrOffset,
125 HashSize: header.HashSize,
126 HashOffset: header.HashOffset,
127 }
128 for i := 0; i < int(header.MsgIdCount); i++ {
129 if _, err := r.Seek(int64(msgIdStart[i]), 0); err != nil {
130 return nil, fmt.Errorf("gettext: %v", err)
131 }
132 msgIdData := make([]byte, msgIdLen[i])
133 if _, err := r.Read(msgIdData); err != nil {
134 return nil, fmt.Errorf("gettext: %v", err)
135 }
136
137 if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
138 return nil, fmt.Errorf("gettext: %v", err)
139 }
140 msgStrData := make([]byte, msgStrLen[i])
141 if _, err := r.Read(msgStrData); err != nil {
142 return nil, fmt.Errorf("gettext: %v", err)
143 }
144
145 if len(msgIdData) == 0 {
146 var msg = Message{
147 MsgId: string(msgIdData),
148 MsgStr: string(msgStrData),
149 }
150 file.MimeHeader.fromMessage(&msg)
151 } else {
152 var msg = Message{
153 MsgId: string(msgIdData),
154 MsgStr: string(msgStrData),
155 }
156
157 if idx := strings.Index(msg.MsgId, EotSeparator); idx != -1 {
158 msg.MsgContext, msg.MsgId = msg.MsgId[:idx], msg.MsgId[idx+1:]
159 }
160
161 if idx := strings.Index(msg.MsgId, NulSeparator); idx != -1 {
162 msg.MsgId, msg.MsgIdPlural = msg.MsgId[:idx], msg.MsgId[idx+1:]
163 msg.MsgStrPlural = strings.Split(msg.MsgStr, NulSeparator)
164 msg.MsgStr = ""
165 }
166 file.Messages = append(file.Messages, msg)
167 }
168 }
169
170 return file, nil
171 }
172
173
174 func (f *File) Save(name string) error {
175 return ioutil.WriteFile(name, f.Data(), 0666)
176 }
177
178
179 func (f *File) Data() []byte {
180 return encodeFile(f)
181 }
182
183
184 func (f *File) String() string {
185 var buf bytes.Buffer
186 fmt.Fprintf(&buf, "# version: %d.%d\n", f.MajorVersion, f.MinorVersion)
187 fmt.Fprintf(&buf, "%s\n", f.MimeHeader.String())
188 fmt.Fprintf(&buf, "\n")
189
190 for k, v := range f.Messages {
191 fmt.Fprintf(&buf, `msgid "%v"`+"\n", k)
192 fmt.Fprintf(&buf, `msgstr "%s"`+"\n", v.MsgStr)
193 fmt.Fprintf(&buf, "\n")
194 }
195
196 return buf.String()
197 }
198
View as plain text