First commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package xmlcheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var cp1251Table = [128]rune{
|
||||
0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, // 80-87
|
||||
0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, // 88-8F
|
||||
0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, // 90-97
|
||||
0xFFFD, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, // 98-9F
|
||||
0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, // A0-A7
|
||||
0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, // A8-AF
|
||||
0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, // B0-B7
|
||||
0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, // B8-BF
|
||||
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, // C0-C7
|
||||
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, // C8-CF
|
||||
0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, // D0-D7
|
||||
0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, // D8-DF
|
||||
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, // E0-E7
|
||||
0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, // E8-EF
|
||||
0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, // F0-F7
|
||||
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, // F8-FF
|
||||
}
|
||||
|
||||
func charsetReader(name string, r io.Reader) (io.Reader, error) {
|
||||
switch name {
|
||||
case "windows-1251", "Windows-1251", "WINDOWS-1251", "cp1251", "CP1251", "windows1251":
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]byte, 0, len(data)*2)
|
||||
for i, b := range data {
|
||||
if b < 0x80 {
|
||||
result = append(result, b)
|
||||
continue
|
||||
}
|
||||
runeValue := cp1251Table[b-0x80]
|
||||
if runeValue == utf8.RuneError {
|
||||
return nil, fmt.Errorf("неопределённый байт Windows-1251 0x%02X по смещению %d", b, i)
|
||||
}
|
||||
result = utf8.AppendRune(result, runeValue)
|
||||
}
|
||||
return bytes.NewReader(result), nil
|
||||
case "utf-8", "UTF-8", "utf8", "UTF8":
|
||||
return r, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("неподдерживаемая кодировка XML %q", name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package xmlcheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Validate(source []byte) error {
|
||||
decoder := xml.NewDecoder(bytes.NewReader(source))
|
||||
decoder.CharsetReader = charsetReader
|
||||
decoder.Strict = true
|
||||
depth, roots := 0, 0
|
||||
for {
|
||||
token, err := decoder.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("некорректный XML: %w", err)
|
||||
}
|
||||
switch node := token.(type) {
|
||||
case xml.StartElement:
|
||||
if depth == 0 {
|
||||
roots++
|
||||
if roots > 1 {
|
||||
return fmt.Errorf("найдено более одного корневого XML-элемента")
|
||||
}
|
||||
}
|
||||
depth++
|
||||
case xml.EndElement:
|
||||
depth--
|
||||
case xml.CharData:
|
||||
if depth == 0 && strings.TrimSpace(string(node)) != "" {
|
||||
return fmt.Errorf("текст за пределами корневого XML-элемента")
|
||||
}
|
||||
}
|
||||
}
|
||||
if roots != 1 || depth != 0 {
|
||||
return fmt.Errorf("нет единственного завершённого корневого XML-элемента")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user