file.go 5.1 KB

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