From e2720c09f7cb30f3dc4a01e3f70292dae34a06a2 Mon Sep 17 00:00:00 2001 From: fontvielle Date: Thu, 14 May 2026 10:49:39 +0300 Subject: [PATCH] =?UTF-8?q?refactor(nsdxml):=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D1=8C=20=D1=81=D0=BE=D0=B1=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=B4=D0=B5=D0=BA?= =?UTF-8?q?=20CP1251=20=D0=BD=D0=B0=20golang.org/x/text/encoding/charmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После добавления 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) --- go.mod | 2 + go.sum | 2 + internal/nsdxml/README.md | 12 +++-- internal/nsdxml/codec.go | 94 +++++++-------------------------------- 4 files changed, 25 insertions(+), 85 deletions(-) create mode 100644 go.sum diff --git a/go.mod b/go.mod index 8127697..b22a59a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.zetit.ru/zuevav/Bridge-and-Join-s go 1.23 + +require golang.org/x/text v0.22.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a81672a --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/nsdxml/README.md b/internal/nsdxml/README.md index 541daaf..16ffb5f 100644 --- a/internal/nsdxml/README.md +++ b/internal/nsdxml/README.md @@ -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` и пр.) заменено на штатную реализацию. ## Тесты diff --git a/internal/nsdxml/codec.go b/internal/nsdxml/codec.go index 80d6c76..31a822c 100644 --- a/internal/nsdxml/codec.go +++ b/internal/nsdxml/codec.go @@ -7,103 +7,41 @@ import ( "fmt" "io" "strings" - "unicode/utf8" + + "golang.org/x/text/encoding/charmap" ) // xmlProlog — пролог windows-1251 XML, который мы пишем при сериализации. const xmlProlog = `` + "\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 }