617 lines
22 KiB
Go
617 lines
22 KiB
Go
package gomodules
|
||
|
||
import (
|
||
"fmt"
|
||
"rubric/utils"
|
||
)
|
||
|
||
var CompositeModule = Module{
|
||
Key: "composite",
|
||
Aliases: []string{"c", "comp", "slice", "map", "struct"},
|
||
Title: "Составные типы",
|
||
Description: "Массивы, срезы, append, отображения, структуры, JSON и HTML",
|
||
Run: showComposite,
|
||
}
|
||
|
||
func showComposite() {
|
||
utils.Header("Go — Составные типы")
|
||
|
||
// ─── Массивы ──────────────────────────────────────────────────
|
||
utils.Section("Массивы (Arrays)")
|
||
|
||
utils.Println("Массив — фиксированной длины последовательность элементов одного типа.")
|
||
utils.Println("Длина является частью типа: " +
|
||
utils.ColorBold + utils.ColorCyan + "[3]int" + utils.ColorReset +
|
||
" и " +
|
||
utils.ColorBold + utils.ColorCyan + "[4]int" + utils.ColorReset +
|
||
" — разные типы.")
|
||
fmt.Println()
|
||
utils.Code(`// Объявление:
|
||
var a [3]int // [0 0 0] — нулевые значения
|
||
var b [3]int = [3]int{1, 2, 3}
|
||
c := [3]int{1, 2, 3}
|
||
d := [...]int{1, 2, 3, 4, 5} // компилятор считает длину сам
|
||
|
||
// Инициализация с индексами:
|
||
e := [5]string{1: "один", 3: "три"} // остальные — ""
|
||
|
||
// Доступ:
|
||
fmt.Println(c[0]) // 1
|
||
fmt.Println(len(c)) // 3
|
||
|
||
// Итерация:
|
||
for i, v := range c {
|
||
fmt.Printf("c[%d] = %d\n", i, v)
|
||
}`)
|
||
|
||
utils.Println(utils.ColorBold + "Сравнение массивов:" + utils.ColorReset)
|
||
utils.Code(`// Массивы можно сравнивать через == и !=
|
||
// (если элементы сравниваемы):
|
||
a := [3]int{1, 2, 3}
|
||
b := [3]int{1, 2, 3}
|
||
fmt.Println(a == b) // true
|
||
|
||
// Передача в функцию — КОПИЯ:
|
||
func double(arr [3]int) { arr[0] *= 2 } // оригинал не меняется
|
||
// Чтобы изменить — передавать указатель: *[3]int`)
|
||
|
||
utils.Note("Массивы в Go используются редко — чаще работают со срезами.")
|
||
|
||
// ─── Срезы ────────────────────────────────────────────────────
|
||
utils.Section("Срезы (Slices)")
|
||
|
||
utils.Println("Срез — динамический вид на массив. Три компонента:")
|
||
utils.Code(`// Указатель на массив + длина (len) + ёмкость (cap)
|
||
//
|
||
// ┌─────────────────────────────────────────┐
|
||
// │ ptr │ len │ cap │
|
||
// └───┬───┴───────┴─────────────────────────┘
|
||
// │
|
||
// ▼
|
||
// ┌───┬───┬───┬───┬───┐
|
||
// │ 0 │ 1 │ 2 │ 3 │ 4 │ ← underlying array
|
||
// └───┴───┴───┴───┴───┘`)
|
||
|
||
utils.Println(utils.ColorBold + "Создание срезов:" + utils.ColorReset)
|
||
utils.Code(`// Из литерала (неявно создаёт массив):
|
||
s := []int{1, 2, 3, 4, 5}
|
||
|
||
// Срез нулевых значений через make(тип, len, cap):
|
||
s := make([]int, 5) // len=5, cap=5
|
||
s := make([]int, 3, 10) // len=3, cap=10
|
||
|
||
// Нулевой срез (nil slice):
|
||
var s []int // s == nil, len=0, cap=0
|
||
fmt.Println(s == nil) // true
|
||
// nil срез безопасен для append и range
|
||
|
||
// Срез из массива:
|
||
a := [5]int{1, 2, 3, 4, 5}
|
||
s := a[1:4] // элементы 1,2,3 → [2 3 4]
|
||
// len=3, cap=4 (до конца массива)`)
|
||
|
||
utils.Println(utils.ColorBold + "Операции срезов:" + utils.ColorReset)
|
||
utils.Code(`s := []int{1, 2, 3, 4, 5}
|
||
|
||
// Взятие среза: s[low:high] (low включён, high — нет)
|
||
s[1:3] // [2 3] len=2
|
||
s[:3] // [1 2 3] low=0
|
||
s[2:] // [3 4 5] high=len
|
||
s[:] // копия ссылки на весь срез
|
||
|
||
// Трёхиндексный срез: s[low:high:max] — ограничивает cap:
|
||
s[1:3:4] // len=2, cap=3
|
||
|
||
// len и cap:
|
||
fmt.Println(len(s), cap(s)) // 5 5
|
||
|
||
// Срезы разделяют память с оригиналом!
|
||
a := []int{1, 2, 3, 4, 5}
|
||
b := a[1:3] // b = [2 3]
|
||
b[0] = 99
|
||
fmt.Println(a) // [1 99 3 4 5] — оригинал изменён!`)
|
||
|
||
utils.Println(utils.ColorBold + "Копирование срезов (copy):" + utils.ColorReset)
|
||
utils.Code(`src := []int{1, 2, 3, 4, 5}
|
||
dst := make([]int, 3)
|
||
n := copy(dst, src) // копирует min(len(dst), len(src)) элементов
|
||
fmt.Println(dst, n) // [1 2 3] 3
|
||
|
||
// Полная независимая копия:
|
||
clone := make([]int, len(src))
|
||
copy(clone, src)
|
||
|
||
// Или через append:
|
||
clone := append([]int{}, src...)`)
|
||
|
||
utils.Println(utils.ColorBold + "Итерация:" + utils.ColorReset)
|
||
utils.Code(`s := []string{"a", "b", "c"}
|
||
|
||
for i, v := range s { // v — копия элемента
|
||
fmt.Println(i, v)
|
||
}
|
||
|
||
for i := range s { // только индексы
|
||
s[i] = strings.ToUpper(s[i])
|
||
}
|
||
|
||
for _, v := range s { // только значения
|
||
fmt.Println(v)
|
||
}`)
|
||
|
||
// ─── append ───────────────────────────────────────────────────
|
||
utils.Section("Функция append")
|
||
|
||
utils.Println("append добавляет элементы в срез, возвращая новый (возможно, с новым массивом).")
|
||
utils.Code(`// Добавить элементы:
|
||
s := []int{1, 2, 3}
|
||
s = append(s, 4) // [1 2 3 4]
|
||
s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7]
|
||
|
||
// Добавить другой срез (оператор ...):
|
||
other := []int{8, 9}
|
||
s = append(s, other...) // [1 2 3 4 5 6 7 8 9]
|
||
|
||
// Удалить элемент по индексу i:
|
||
i := 2
|
||
s = append(s[:i], s[i+1:]...) // порядок сохраняется
|
||
// Быстрое удаление (порядок не важен):
|
||
s[i] = s[len(s)-1]
|
||
s = s[:len(s)-1]
|
||
|
||
// Вставить элемент по индексу i:
|
||
s = append(s[:i+1], s[i:]...)
|
||
s[i] = newValue
|
||
|
||
// Стек (stack):
|
||
push := func(s []int, v int) []int { return append(s, v) }
|
||
pop := func(s []int) ([]int, int) { return s[:len(s)-1], s[len(s)-1] }`)
|
||
|
||
utils.Println(utils.ColorBold + "Механизм роста ёмкости:" + utils.ColorReset)
|
||
utils.Code(`// При нехватке cap, append выделяет новый массив (~удваивает cap):
|
||
var s []int
|
||
for i := 0; i < 8; i++ {
|
||
s = append(s, i)
|
||
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
|
||
}
|
||
// len=1 cap=1 [0]
|
||
// len=2 cap=2 [0 1]
|
||
// len=3 cap=4 [0 1 2]
|
||
// len=4 cap=4 [0 1 2 3]
|
||
// len=5 cap=8 [0 1 2 3 4]
|
||
// ...`)
|
||
|
||
utils.Note("Всегда присваивай результат append обратно: s = append(s, v)")
|
||
utils.Note("Передавай срез с запасом через make([]T, 0, n) если знаешь итоговый размер — избежишь лишних аллокаций.")
|
||
|
||
// ─── Отображения ──────────────────────────────────────────────
|
||
utils.Section("Отображения (Maps)")
|
||
|
||
utils.Println("Map — ссылочный тип, хэш-таблица. Порядок итерации не гарантирован.")
|
||
fmt.Println()
|
||
utils.Println(utils.ColorBold + "Создание:" + utils.ColorReset)
|
||
utils.Code(`// Через литерал:
|
||
ages := map[string]int{
|
||
"Alice": 30,
|
||
"Bob": 25,
|
||
}
|
||
|
||
// Через make:
|
||
ages := make(map[string]int)
|
||
ages := make(map[string]int, 100) // с подсказкой ёмкости
|
||
|
||
// Нулевое значение — nil:
|
||
var m map[string]int // m == nil
|
||
// m["key"] = 1 // паника! нельзя писать в nil map
|
||
// fmt.Println(m["key"]) // 0 — читать можно, не паникует`)
|
||
|
||
utils.Println(utils.ColorBold + "Операции с map:" + utils.ColorReset)
|
||
utils.Code(`ages := map[string]int{"Alice": 30, "Bob": 25}
|
||
|
||
// Запись:
|
||
ages["Carol"] = 28
|
||
ages["Alice"] = 31 // обновить
|
||
|
||
// Чтение:
|
||
age := ages["Alice"] // 31
|
||
age = ages["unknown"] // 0 — нулевое значение, НЕ ошибка
|
||
|
||
// Проверка наличия ключа (comma ok idiom):
|
||
age, ok := ages["Alice"]
|
||
if ok {
|
||
fmt.Println("Alice:", age)
|
||
}
|
||
// Коротко:
|
||
if age, ok := ages["Alice"]; ok {
|
||
fmt.Println("Alice:", age)
|
||
}
|
||
|
||
// Удаление:
|
||
delete(ages, "Bob")
|
||
delete(ages, "nonexistent") // безопасно, нет ошибки
|
||
|
||
// Длина:
|
||
fmt.Println(len(ages)) // 2
|
||
|
||
// Итерация (порядок случаен!):
|
||
for name, age := range ages {
|
||
fmt.Printf("%s: %d\n", name, age)
|
||
}
|
||
|
||
// Итерация по отсортированным ключам:
|
||
keys := make([]string, 0, len(ages))
|
||
for k := range ages { keys = append(keys, k) }
|
||
sort.Strings(keys)
|
||
for _, k := range keys { fmt.Println(k, ages[k]) }`)
|
||
|
||
utils.Println(utils.ColorBold + "Сравнение map:" + utils.ColorReset)
|
||
utils.Code(`// Map нельзя сравнивать через == (только с nil):
|
||
var m map[string]int
|
||
fmt.Println(m == nil) // true
|
||
|
||
// Сравнение содержимого — через reflect.DeepEqual или вручную:
|
||
import "reflect"
|
||
fmt.Println(reflect.DeepEqual(map1, map2))
|
||
|
||
// Или maps.Equal из Go 1.21:
|
||
import "maps"
|
||
fmt.Println(maps.Equal(map1, map2))`)
|
||
|
||
utils.Println(utils.ColorBold + "Типичные паттерны:" + utils.ColorReset)
|
||
utils.Code(`// Множество (set):
|
||
seen := make(map[string]bool)
|
||
seen["Alice"] = true
|
||
if seen["Alice"] { ... }
|
||
|
||
// Подсчёт слов:
|
||
words := strings.Fields(text)
|
||
count := make(map[string]int)
|
||
for _, w := range words { count[w]++ }
|
||
|
||
// Группировка:
|
||
byDept := make(map[string][]Employee)
|
||
for _, e := range employees {
|
||
byDept[e.Dept] = append(byDept[e.Dept], e)
|
||
}`)
|
||
|
||
// ─── Структуры ────────────────────────────────────────────────
|
||
utils.Section("Структуры (Structs)")
|
||
|
||
utils.Println("Struct — агрегатный тип, объединяет поля разных типов.")
|
||
utils.Code(`// Объявление типа:
|
||
type Point struct {
|
||
X, Y float64
|
||
}
|
||
|
||
type Employee struct {
|
||
ID int
|
||
Name string
|
||
Salary float64
|
||
Manager *Employee // рекурсивный тип — только указатель
|
||
}`)
|
||
|
||
utils.Println(utils.ColorBold + "Создание и доступ:" + utils.ColorReset)
|
||
utils.Code(`// Структурный литерал (по именам — рекомендуется):
|
||
p := Point{X: 1.0, Y: 2.0}
|
||
p := Point{1.0, 2.0} // по позиции (хрупко, не для публичных типов)
|
||
|
||
// Нулевое значение:
|
||
var p Point // Point{X:0, Y:0}
|
||
|
||
// Доступ к полям:
|
||
fmt.Println(p.X) // 1.0
|
||
p.Y = 5.0
|
||
|
||
// Указатель на структуру:
|
||
pp := &Point{1.0, 2.0}
|
||
pp.X = 10 // (*pp).X = 10 — автодеференс
|
||
fmt.Println(pp.X) // 10
|
||
|
||
// Через new:
|
||
pp := new(Point) // *Point, поля = нули
|
||
pp.X = 1.0`)
|
||
|
||
utils.Println(utils.ColorBold + "Сравнение структур:" + utils.ColorReset)
|
||
utils.Code(`// Структуры сравниваемы если все поля сравниваемы:
|
||
type Point struct{ X, Y int }
|
||
p1 := Point{1, 2}
|
||
p2 := Point{1, 2}
|
||
fmt.Println(p1 == p2) // true
|
||
|
||
// Поля-срезы, map, func — делают структуру несравниваемой.
|
||
// Используй reflect.DeepEqual для глубокого сравнения.
|
||
|
||
// Структура как ключ map (только если сравниваема):
|
||
type Coord struct{ Lat, Lon float64 }
|
||
visited := map[Coord]bool{}
|
||
visited[Coord{55.75, 37.62}] = true`)
|
||
|
||
// ─── Встраивание и анонимные поля ────────────────────────────
|
||
utils.Section("Встраивание структур и анонимные поля")
|
||
|
||
utils.Println("Встраивание (embedding) — включение типа без имени поля.")
|
||
utils.Println("Поля и методы встроенного типа «поднимаются» во внешний тип.")
|
||
utils.Code(`type Animal struct {
|
||
Name string
|
||
}
|
||
|
||
func (a Animal) Speak() string {
|
||
return a.Name + " говорит"
|
||
}
|
||
|
||
type Dog struct {
|
||
Animal // анонимное поле — встраивание
|
||
Breed string
|
||
}
|
||
|
||
d := Dog{
|
||
Animal: Animal{Name: "Шарик"},
|
||
Breed: "Лабрадор",
|
||
}
|
||
|
||
// Продвинутый доступ (promotion):
|
||
fmt.Println(d.Name) // "Шарик" (d.Animal.Name)
|
||
fmt.Println(d.Speak()) // "Шарик говорит" (d.Animal.Speak())
|
||
fmt.Println(d.Breed) // "Лабрадор"
|
||
// Явный доступ:
|
||
fmt.Println(d.Animal.Name)`)
|
||
|
||
utils.Code(`// Несколько встраиваний:
|
||
type Logger struct{ Prefix string }
|
||
func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) }
|
||
|
||
type Server struct {
|
||
Logger // анонимное поле
|
||
net.Listener // встроен интерфейс
|
||
Host string
|
||
}
|
||
|
||
s := Server{Logger: Logger{Prefix: "[server]"}, Host: "localhost"}
|
||
s.Log("started") // s.Logger.Log("started")
|
||
|
||
// Конфликт имён — нужно явно указывать путь:
|
||
type A struct{ X int }
|
||
type B struct{ X int }
|
||
type C struct { A; B }
|
||
c := C{}
|
||
// c.X — ошибка компиляции: ambiguous
|
||
c.A.X = 1 // явно`)
|
||
|
||
utils.Code(`// Встраивание указателя:
|
||
type Cache struct {
|
||
*sync.Mutex // анонимный указатель
|
||
data map[string]string
|
||
}
|
||
|
||
c := Cache{
|
||
Mutex: &sync.Mutex{},
|
||
data: make(map[string]string),
|
||
}
|
||
c.Lock() // c.Mutex.Lock()
|
||
c.data["key"] = "value"
|
||
c.Unlock()`)
|
||
|
||
utils.Note("Встраивание — это композиция, не наследование. Go не имеет наследования.")
|
||
|
||
// ─── Работа с JSON ────────────────────────────────────────────
|
||
utils.Section("Работа с JSON")
|
||
|
||
utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "encoding/json" + utils.ColorReset +
|
||
" — маршалинг и демаршалинг JSON.")
|
||
fmt.Println()
|
||
utils.Println(utils.ColorBold + "Теги структур:" + utils.ColorReset)
|
||
utils.Code(`type Person struct {
|
||
Name string ` + "`" + `json:"name"` + "`" + `
|
||
Age int ` + "`" + `json:"age"` + "`" + `
|
||
Email string ` + "`" + `json:"email,omitempty"` + "`" + ` // пропустить если пусто
|
||
Phone string ` + "`" + `json:"-"` + "`" + ` // никогда не включать
|
||
private string // неэкспортируемые — игнорируются
|
||
}
|
||
|
||
// Теги:
|
||
// json:"name" — имя в JSON
|
||
// json:"name,omitempty"— пропустить нулевые значения
|
||
// json:"-" — исключить поле`)
|
||
|
||
utils.Println(utils.ColorBold + "Маршалинг (структура → JSON):" + utils.ColorReset)
|
||
utils.Code(`import "encoding/json"
|
||
|
||
p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
|
||
|
||
// Сериализация:
|
||
data, err := json.Marshal(p)
|
||
if err != nil { log.Fatal(err) }
|
||
fmt.Println(string(data))
|
||
// {"name":"Alice","age":30,"email":"alice@example.com"}
|
||
|
||
// Красивый вывод с отступами:
|
||
data, err = json.MarshalIndent(p, "", " ")
|
||
fmt.Println(string(data))
|
||
// {
|
||
// "name": "Alice",
|
||
// "age": 30,
|
||
// "email": "alice@example.com"
|
||
// }`)
|
||
|
||
utils.Println(utils.ColorBold + "Демаршалинг (JSON → структура):" + utils.ColorReset)
|
||
utils.Code(`jsonStr := ` + "`" + `{"name":"Bob","age":25}` + "`" + `
|
||
|
||
var p Person
|
||
if err := json.Unmarshal([]byte(jsonStr), &p); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
fmt.Println(p.Name, p.Age) // Bob 25
|
||
|
||
// Потоковое чтение из http.Response:
|
||
resp, _ := http.Get("https://api.example.com/users")
|
||
defer resp.Body.Close()
|
||
var users []Person
|
||
json.NewDecoder(resp.Body).Decode(&users)
|
||
|
||
// Потоковая запись в http.ResponseWriter:
|
||
json.NewEncoder(w).Encode(users)`)
|
||
|
||
utils.Println(utils.ColorBold + "Произвольный JSON (interface{} / any):" + utils.ColorReset)
|
||
utils.Code(`// Когда структура неизвестна:
|
||
var result map[string]any
|
||
json.Unmarshal(data, &result)
|
||
|
||
name := result["name"].(string) // type assertion
|
||
age := result["age"].(float64) // числа → float64 по умолчанию
|
||
|
||
// Проверка типа:
|
||
if s, ok := result["name"].(string); ok {
|
||
fmt.Println(s)
|
||
}
|
||
|
||
// json.RawMessage — отложенный демаршалинг:
|
||
type Response struct {
|
||
Status string ` + "`" + `json:"status"` + "`" + `
|
||
Payload json.RawMessage ` + "`" + `json:"payload"` + "`" + `
|
||
}
|
||
var resp Response
|
||
json.Unmarshal(data, &resp)
|
||
var user Person
|
||
json.Unmarshal(resp.Payload, &user)`)
|
||
|
||
utils.Println(utils.ColorBold + "Кастомные маршалеры:" + utils.ColorReset)
|
||
utils.Code(`// Реализовать json.Marshaler / json.Unmarshaler:
|
||
type Duration time.Duration
|
||
|
||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||
return json.Marshal(time.Duration(d).String())
|
||
}
|
||
|
||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||
var s string
|
||
if err := json.Unmarshal(b, &s); err != nil { return err }
|
||
dur, err := time.ParseDuration(s)
|
||
*d = Duration(dur)
|
||
return err
|
||
}`)
|
||
|
||
// ─── Работа с HTML ────────────────────────────────────────────
|
||
utils.Section("Работа с HTML")
|
||
|
||
utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "html/template" + utils.ColorReset +
|
||
" — безопасные HTML-шаблоны с автоматическим экранированием.")
|
||
fmt.Println()
|
||
utils.Code(`import "html/template"
|
||
|
||
// Шаблон:
|
||
const tmpl = ` + "`" + `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<body>
|
||
<h1>Привет, {{.Name}}!</h1>
|
||
<ul>
|
||
{{range .Items}}
|
||
<li>{{.}}</li>
|
||
{{end}}
|
||
</ul>
|
||
{{if .Admin}}
|
||
<p>Вы администратор</p>
|
||
{{end}}
|
||
</body>
|
||
</html>` + "`" + `
|
||
|
||
type Data struct {
|
||
Name string
|
||
Items []string
|
||
Admin bool
|
||
}
|
||
|
||
// Парсинг и выполнение:
|
||
t := template.Must(template.New("page").Parse(tmpl))
|
||
|
||
data := Data{
|
||
Name: "Alice",
|
||
Items: []string{"Go", "Docker", "Kubernetes"},
|
||
Admin: true,
|
||
}
|
||
|
||
// В os.Stdout:
|
||
t.Execute(os.Stdout, data)
|
||
|
||
// В http.ResponseWriter:
|
||
func handler(w http.ResponseWriter, r *http.Request) {
|
||
t.Execute(w, data)
|
||
}`)
|
||
|
||
utils.Println(utils.ColorBold + "Экранирование и безопасность:" + utils.ColorReset)
|
||
utils.Code(`// html/template автоматически экранирует пользовательский ввод:
|
||
// <script>alert(1)</script> → <script>alert(1)</script>
|
||
|
||
// Если нужно вставить безопасный HTML — использовать template.HTML:
|
||
type Page struct {
|
||
Title string
|
||
Content template.HTML // не экранируется
|
||
}
|
||
|
||
// Никогда не делай:
|
||
// template.HTML(userInput) — XSS!
|
||
|
||
// text/template — без экранирования (для не-HTML):
|
||
import "text/template"
|
||
// Используй для генерации кода, конфигов, emails в plain text`)
|
||
|
||
utils.Println(utils.ColorBold + "Шаблонные функции и Actions:" + utils.ColorReset)
|
||
utils.Code(`// Функции в шаблоне:
|
||
funcMap := template.FuncMap{
|
||
"upper": strings.ToUpper,
|
||
"add": func(a, b int) int { return a + b },
|
||
}
|
||
t := template.New("t").Funcs(funcMap).Parse(` + "`" + `
|
||
{{.Name | upper}}
|
||
{{add 2 3}}
|
||
` + "`" + `)
|
||
|
||
// Actions (действия):
|
||
{{/* комментарий */}}
|
||
{{.Field}} // вывести поле
|
||
{{.Method arg}} // вызов метода
|
||
{{if .Cond}} ... {{end}}
|
||
{{if .Cond}} ... {{else}} ... {{end}}
|
||
{{range .Slice}} {{.}} {{end}}
|
||
{{range .Map}} {{.}} {{end}}
|
||
{{with .Val}} {{.}} {{end}} // установить . = Val если не ноль
|
||
{{template "name" .}} // вложенный шаблон
|
||
|
||
// Шаблоны из файлов:
|
||
t := template.Must(template.ParseFiles("index.html", "base.html"))
|
||
t := template.Must(template.ParseGlob("templates/*.html"))`)
|
||
|
||
utils.Println(utils.ColorBold + "Парсинг HTML (html пакет):" + utils.ColorReset)
|
||
utils.Code(`import "html"
|
||
|
||
// Экранирование и разэкранирование HTML-сущностей:
|
||
escaped := html.EscapeString("<script>alert('xss')</script>")
|
||
// <script>alert('xss')</script>
|
||
|
||
unescaped := html.UnescapeString("<b>жирный</b>")
|
||
// <b>жирный</b>
|
||
|
||
// Для разбора HTML-дерева — golang.org/x/net/html:
|
||
import "golang.org/x/net/html"
|
||
|
||
resp, _ := http.Get("https://example.com")
|
||
doc, _ := html.Parse(resp.Body)
|
||
|
||
// Рекурсивный обход:
|
||
var walk func(*html.Node)
|
||
walk = func(n *html.Node) {
|
||
if n.Type == html.ElementNode && n.Data == "a" {
|
||
for _, attr := range n.Attr {
|
||
if attr.Key == "href" {
|
||
fmt.Println(attr.Val)
|
||
}
|
||
}
|
||
}
|
||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||
walk(c)
|
||
}
|
||
}
|
||
walk(doc)`)
|
||
|
||
utils.Footer()
|
||
}
|