golang. Ошибка при анмаршалинге не прямого xml

При вписывании XML-данных напрямую в переменную, как в коде ниже все выводится как и должно.

package main

import (
    "encoding/xml"
    "fmt"
    "net/http"
    "io"
    "log"
)

type ValCurs struct {
    XMLName xml.Name `xml:"ValCurs"`
    Valute  []struct {
        NumCode   string `xml:"NumCode"`
        CharCode  string `xml:"CharCode"`
        Nominal   string `xml:"Nominal"`
        Name      string `xml:"Name"`
        Value     string `xml:"Value"`
        VunitRate string `xml:"VunitRate"`
    } `xml:"Valute"`
}

func main() {

    data := `
    <ValCurs Date="26.10.2024" name="Foreign Currency Market">
<Valute ID="R01010">
<NumCode>036</NumCode>
<CharCode>AUD</CharCode>
<Nominal>1</Nominal>
<Name>Австралийский доллар</Name>
<Value>64,0024</Value>
<VunitRate>64,0024</VunitRate>
</Valute>
</ValCurs>
`

    valCurs := new(ValCurs)

    err := xml.Unmarshal([]byte(data), valCurs)
    if err != nil {
        log.Fatal(err)
    }

    for _, valute := range valCurs.Valute {
        fmt.Printf("%s: 1 %s = %v\n", valute.CharCode, valute.Name, valute.Value)

    }
}

Но при попытке получить их через get-запрос выводится ошибка

 XML syntax error on line 1: unquoted or missing attribute value in element

Никак не могу понять как ее исправить. Вот код выдающий ошибку

package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "log"
    "net/http"
)

type ValCurs struct {
    XMLName xml.Name `xml:"ValCurs"`
    Valute  []struct {
        NumCode   string `xml:"NumCode"`
        CharCode  string `xml:"CharCode"`
        Nominal   string `xml:"Nominal"`
        Name      string `xml:"Name"`
        Value     string `xml:"Value"`
        VunitRate string `xml:"VunitRate"`
    } `xml:"Valute"`
}

func main() {
    url := "https://cbr.ru/scripts/XML_daily.asp?date_req=27/10/2024"
    res, err := http.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()

    data, err := io.ReadAll(res.Body)
    if err != nil {
        log.Fatal(err)
    }

    valCurs := new(ValCurs)

    err = xml.Unmarshal([]byte(data), valCurs)
    if err != nil {
        log.Fatal(err)
    }

    for _, valute := range valCurs.Valute {
        fmt.Printf("%s: 1 %s = %v\n", valute.CharCode, valute.Name, valute.Value)

    }
}

Ответы (1 шт):

Автор решения: Pak Uula

В вашем вопросе сразу несколько проблем.

Во-первых, сайт Центробанка возвращает не XML документ, а HTML страницу с ошибкой 403. Нужно правильным образом подобрать заголовки, чтобы Центробанк вернул именно то что вам нужно

Во-вторых, документ возвращается в кодировке Windows-1251. Вам нужно настроить XML parser на разбор документов в не-юникодной кодировке

Вот мой вариант. Это приложеньице принимает флаг -file FILENAME чтобы парсить документ из файла. По-умолчанию парсится документ из интернета.

Для того, чтобы Центробанк отдал документ, приложение притворяется curl-ом.

package main

import (
    "bytes"
    "encoding/xml"
    "flag"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"

    "golang.org/x/net/html/charset"
)

var (
    fileFlag = flag.String("file", "", "Parse from file")
)

type ValCurs struct {
    XMLName xml.Name `xml:"ValCurs"`
    Valute  []struct {
        NumCode   string `xml:"NumCode"`
        CharCode  string `xml:"CharCode"`
        Nominal   string `xml:"Nominal"`
        Name      string `xml:"Name"`
        Value     string `xml:"Value"`
        VunitRate string `xml:"VunitRate"`
    } `xml:"Valute"`
}

func main() {
    flag.Parse()

    data := bytes.Buffer{}
    if *fileFlag != "" {
        file, err := os.Open(*fileFlag)
        if err != nil {
            panic(err)
        }
        io.Copy(&data, file)
    } else {
        client := &http.Client{}

        req, err := http.NewRequest("GET", "https://cbr.ru/scripts/XML_daily.asp?date_req=27/10/2024", nil)
        if err != nil {
            log.Fatalln(err)
        }

        req.Header.Set("User-Agent", "curl/7.54.1")

        res, err := client.Do(req)
        if err != nil {
            log.Fatal(err)
        }
        defer res.Body.Close()

        io.Copy(&data, res.Body)
    }
    // fmt.Println(data.String())
    
    valCurs := new(ValCurs)
    // Handle windows-1251
    decoder := xml.NewDecoder(&data)
    decoder.CharsetReader = charset.NewReaderLabel

    err := decoder.Decode(valCurs)
    if err != nil {
        log.Fatal(err)
    }

    for _, valute := range valCurs.Valute {
        fmt.Printf("%s: 1 %s = %v\n", valute.CharCode, valute.Name, valute.Value)

    }
}
→ Ссылка