123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- // Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package mo
- import (
- "bytes"
- "encoding/binary"
- "fmt"
- "io/ioutil"
- "strings"
- )
- const (
- MoHeaderSize = 28
- MoMagicLittleEndian = 0x950412de
- MoMagicBigEndian = 0xde120495
- EotSeparator = "\x04" // msgctxt and msgid separator
- NulSeparator = "\x00" // msgid and msgstr separator
- )
- // File represents an MO File.
- //
- // See http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
- type File struct {
- MagicNumber uint32
- MajorVersion uint16
- MinorVersion uint16
- MsgIdCount uint32
- MsgIdOffset uint32
- MsgStrOffset uint32
- HashSize uint32
- HashOffset uint32
- MimeHeader Header
- Messages []Message
- }
- // Load loads a named mo file.
- func Load(name string) (*File, error) {
- data, err := ioutil.ReadFile(name)
- if err != nil {
- return nil, err
- }
- return LoadData(data)
- }
- // LoadData loads mo file format data.
- func LoadData(data []byte) (*File, error) {
- r := bytes.NewReader(data)
- var magicNumber uint32
- if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- var bo binary.ByteOrder
- switch magicNumber {
- case MoMagicLittleEndian:
- bo = binary.LittleEndian
- case MoMagicBigEndian:
- bo = binary.BigEndian
- default:
- return nil, fmt.Errorf("gettext: %v", "invalid magic number")
- }
- var header struct {
- MajorVersion uint16
- MinorVersion uint16
- MsgIdCount uint32
- MsgIdOffset uint32
- MsgStrOffset uint32
- HashSize uint32
- HashOffset uint32
- }
- if err := binary.Read(r, bo, &header); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- if v := header.MajorVersion; v != 0 && v != 1 {
- return nil, fmt.Errorf("gettext: %v", "invalid version number")
- }
- if v := header.MinorVersion; v != 0 && v != 1 {
- return nil, fmt.Errorf("gettext: %v", "invalid version number")
- }
- msgIdStart := make([]uint32, header.MsgIdCount)
- msgIdLen := make([]uint32, header.MsgIdCount)
- if _, err := r.Seek(int64(header.MsgIdOffset), 0); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- for i := 0; i < int(header.MsgIdCount); i++ {
- if err := binary.Read(r, bo, &msgIdLen[i]); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- if err := binary.Read(r, bo, &msgIdStart[i]); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- }
- msgStrStart := make([]int32, header.MsgIdCount)
- msgStrLen := make([]int32, header.MsgIdCount)
- if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- for i := 0; i < int(header.MsgIdCount); i++ {
- if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- }
- file := &File{
- MagicNumber: magicNumber,
- MajorVersion: header.MajorVersion,
- MinorVersion: header.MinorVersion,
- MsgIdCount: header.MsgIdCount,
- MsgIdOffset: header.MsgIdOffset,
- MsgStrOffset: header.MsgStrOffset,
- HashSize: header.HashSize,
- HashOffset: header.HashOffset,
- }
- for i := 0; i < int(header.MsgIdCount); i++ {
- if _, err := r.Seek(int64(msgIdStart[i]), 0); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- msgIdData := make([]byte, msgIdLen[i])
- if _, err := r.Read(msgIdData); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- msgStrData := make([]byte, msgStrLen[i])
- if _, err := r.Read(msgStrData); err != nil {
- return nil, fmt.Errorf("gettext: %v", err)
- }
- if len(msgIdData) == 0 {
- var msg = Message{
- MsgId: string(msgIdData),
- MsgStr: string(msgStrData),
- }
- file.MimeHeader.fromMessage(&msg)
- } else {
- var msg = Message{
- MsgId: string(msgIdData),
- MsgStr: string(msgStrData),
- }
- // Is this a context message?
- if idx := strings.Index(msg.MsgId, EotSeparator); idx != -1 {
- msg.MsgContext, msg.MsgId = msg.MsgId[:idx], msg.MsgId[idx+1:]
- }
- // Is this a plural message?
- if idx := strings.Index(msg.MsgId, NulSeparator); idx != -1 {
- msg.MsgId, msg.MsgIdPlural = msg.MsgId[:idx], msg.MsgId[idx+1:]
- msg.MsgStrPlural = strings.Split(msg.MsgStr, NulSeparator)
- msg.MsgStr = ""
- }
- file.Messages = append(file.Messages, msg)
- }
- }
- return file, nil
- }
- // Save saves a mo file.
- func (f *File) Save(name string) error {
- return ioutil.WriteFile(name, f.Data(), 0666)
- }
- // Save returns a mo file format data.
- func (f *File) Data() []byte {
- return encodeFile(f)
- }
- // String returns the po format file string.
- func (f *File) String() string {
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "# version: %d.%d\n", f.MajorVersion, f.MinorVersion)
- fmt.Fprintf(&buf, "%s\n", f.MimeHeader.String())
- fmt.Fprintf(&buf, "\n")
- for k, v := range f.Messages {
- fmt.Fprintf(&buf, `msgid "%v"`+"\n", k)
- fmt.Fprintf(&buf, `msgstr "%s"`+"\n", v.MsgStr)
- fmt.Fprintf(&buf, "\n")
- }
- return buf.String()
- }
|