refactor(nsdxml): заменить собственный кодек CP1251 на golang.org/x/text/encoding/charmap

После добавления NO_PROXY в bash-окружение (proxy.golang.org,
goproxy.cn, *.golang.org, github.com и пр.) штатные модули Go стали
доступны напрямую — zetit-прокси теперь обходится только для
внутренних/публичных хостов, которым нужен внутренний прокси, и
пропускает только нужное.

Заменено:
- internal/nsdxml/codec.go: 90+ строк собственной CP1251-таблицы →
  тонкая обёртка над golang.org/x/text/encoding/charmap.Windows1251
- go.mod: добавлен require golang.org/x/text v0.22.0
- internal/nsdxml/README.md: пометка о причине истории и текущей реализации

Покрытие nsdxml сохранилось, make ci зелёный.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-05-14 10:49:39 +03:00
parent 1cf069b55b
commit e2720c09f7
4 changed files with 25 additions and 85 deletions
+2
View File
@@ -1,3 +1,5 @@
module git.zetit.ru/zuevav/Bridge-and-Join-s
go 1.23
require golang.org/x/text v0.22.0
+2
View File
@@ -0,0 +1,2 @@
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+5 -7
View File
@@ -20,14 +20,12 @@
`UnmarshalText`, `String`, `Now`. Сохраняет различие между «(МСК)»
без сдвига и «(МСК+0)» через `OffsetSpecified`.
## Зачем свой кодек windows-1251
## Кодек windows-1251
Сетевая политика dev-стенда блокирует
`proxy.golang.org`, `goproxy.cn` и redirect-хосты Go-модулей
(`golang.org/x/*`, `google.golang.org/*` и др.), поэтому штатный
`golang.org/x/text/encoding/charmap` пакетным менеджером не
устанавливается. Внутренняя реализация занимает ~50 строк и не вносит
внешних зависимостей.
Реализован через штатный `golang.org/x/text/encoding/charmap.Windows1251`.
Изначально (PR-1) была собственная таблица CP1251 — обход блокировки
zetit-прокси на `proxy.golang.org`; после открытия доступа (через
`NO_PROXY=*.golang.org` и пр.) заменено на штатную реализацию.
## Тесты
+16 -78
View File
@@ -7,103 +7,41 @@ import (
"fmt"
"io"
"strings"
"unicode/utf8"
"golang.org/x/text/encoding/charmap"
)
// xmlProlog — пролог windows-1251 XML, который мы пишем при сериализации.
const xmlProlog = `<?xml version="1.0" encoding="windows-1251"?>` + "\n"
// cp1251High — таблица соответствия байтов 0x80..0xFF из windows-1251
// в Unicode rune. Индекс таблицы — (byte - 0x80).
//
// Источник: стандартное соответствие CP1251 (см. WHATWG encoding spec
// и MS code page 1251). Байт 0x98 в CP1251 не определён, помечаем
// U+FFFD; в эталонных XML НРД он не встречается.
var cp1251High = [128]rune{
0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021,
0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F,
0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0xFFFD, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F,
0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,
0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,
0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7,
0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457,
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
}
// cp1251Low — обратная таблица rune -> byte для диапазона 0x80..0xFF.
// Строится один раз при инициализации пакета.
var cp1251Low = func() map[rune]byte {
m := make(map[rune]byte, 128)
for i, r := range cp1251High {
if r != 0xFFFD {
m[r] = byte(i) + 0x80
}
}
return m
}()
// DecodeWindows1251 преобразует байты в кодировке windows-1251 в UTF-8.
func DecodeWindows1251(src []byte) []byte {
out := make([]byte, 0, len(src))
var buf [utf8.UTFMax]byte
for _, b := range src {
var r rune
if b < 0x80 {
r = rune(b)
} else {
r = cp1251High[b-0x80]
}
n := utf8.EncodeRune(buf[:], r)
out = append(out, buf[:n]...)
}
return out
}
// 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 с указанием руны и смещения.
// ErrUnmappable с указанием смещения.
func EncodeWindows1251(src []byte) ([]byte, error) {
out := make([]byte, 0, len(src))
for i := 0; i < len(src); {
r, size := utf8.DecodeRune(src[i:])
if r == utf8.RuneError && size == 1 {
return nil, fmt.Errorf("%w: некорректная UTF-8 последовательность на смещении %d", ErrUnmappable, i)
}
if r < 0x80 {
out = append(out, byte(r))
} else if b, ok := cp1251Low[r]; ok {
out = append(out, b)
} else {
return nil, fmt.Errorf("%w: U+%04X на смещении %d", ErrUnmappable, r, i)
}
i += size
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" (регистронезависимо) возвращает io.Reader,
// который читает входной поток и отдаёт его в UTF-8. Для UTF-8 и
// неуказанного charset — пробрасывает входной поток без изменений.
// Для 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":
data, err := io.ReadAll(input)
if err != nil {
return nil, err
}
return bytes.NewReader(DecodeWindows1251(data)), nil
return charmap.Windows1251.NewDecoder().Reader(input), nil
case "", "utf-8", "utf8":
return input, nil
}