package nsdxml import ( "bytes" "encoding/xml" "errors" "fmt" "io" "strings" "golang.org/x/text/encoding/charmap" ) // xmlProlog — пролог windows-1251 XML, который мы пишем при сериализации. const xmlProlog = `` + "\n" // ErrUnmappable возвращается, когда руна не имеет представления в windows-1251. var ErrUnmappable = errors.New("nsdxml: rune не представим в windows-1251") // DecodeWindows1251 преобразует байты в кодировке windows-1251 в UTF-8. // Использует штатную реализацию golang.org/x/text/encoding/charmap. func DecodeWindows1251(src []byte) []byte { out, _ := charmap.Windows1251.NewDecoder().Bytes(src) return out } // EncodeWindows1251 преобразует UTF-8 байты в windows-1251. Если в // исходной строке встречается руна, не выразимая в CP1251, возвращает // ErrUnmappable с указанием смещения. func EncodeWindows1251(src []byte) ([]byte, error) { out, err := charmap.Windows1251.NewEncoder().Bytes(src) if err != nil { return nil, fmt.Errorf("%w: %w", ErrUnmappable, err) } return out, nil } // CharsetReader пригоден к использованию в xml.Decoder.CharsetReader. // Для charset "windows-1251"/"cp1251" возвращает декодирующий reader. // Для UTF-8 и неуказанного charset — пробрасывает поток без изменений. func CharsetReader(charset string, input io.Reader) (io.Reader, error) { switch strings.ToLower(charset) { case "windows-1251", "cp1251": return charmap.Windows1251.NewDecoder().Reader(input), nil case "", "utf-8", "utf8": return input, nil } return nil, fmt.Errorf("nsdxml: неизвестная кодировка %q", charset) } // Marshal сериализует v в XML, добавляет пролог с encoding="windows-1251" // и преобразует поток в windows-1251. func Marshal(v any) ([]byte, error) { utf8Body, err := xml.MarshalIndent(v, "", "\t") if err != nil { return nil, err } withProlog := append([]byte(xmlProlog), utf8Body...) return EncodeWindows1251(withProlog) } // Unmarshal разбирает XML (windows-1251 или UTF-8) в v. Использует // CharsetReader для перекодирования входа, если в прологе указана // windows-1251. func Unmarshal(data []byte, v any) error { dec := xml.NewDecoder(bytes.NewReader(data)) dec.CharsetReader = CharsetReader return dec.Decode(v) }