Server-Side Scripting/Internet Data/Go

From Wikiversity
Jump to navigation Jump to search

routes/lesson8.go[edit | edit source]

// This program reads JSON data from Wikidata with countries
// and Celsius temperatures. It displays the data in Celsius
// and Fahrenheit sorted in decending order by temperature.
//
// File format:
// Country,MaximumTemperature
// Bulgaria,45.2 °C
// Canada,45 °C
//
// References:
//  https://www.mathsisfun.com/temperature-conversion.html
//  https://golang.org/doc/
//  https://blog.alexellis.io/golang-json-api-client/
//  https://www.sohamkamani.com/golang/json/

package routes

import (
    "encoding/json"
    "html/template"
    "io/ioutil"
    "log"
    "net/http"
    "path/filepath"
    "sort"
	"strconv"
    "strings"
    "time"
)

type Temperature8 struct {
    country     string
    celsius     float64
    fahrenheit  float64
}

func Lesson8(response http.ResponseWriter, request *http.Request) {
	response.Header().Set("Content-Type", "text/html; charset=utf-8")

	type Data struct {
		Table  template.HTML
	}
	
	result := ""

    switch request.Method {
        case "GET":
            result = getData8()
        default:
            result = "Unexpected request method: " + request.Method
    }

	data := Data{template.HTML(result)}
	path := filepath.Join("templates", "lesson8.html")
	parsed, _ := template.ParseFiles(path)
	parsed.Execute(response, data)
}

func getData8() string {
    url := "https://query.wikidata.org/sparql"
    query := `
SELECT DISTINCT ?Country ?MaximumTemperature WHERE {
    ?countryItem wdt:P31 wd:Q6256;
    p:P6591 ?maximumTemperatureRecord.
    ?maximumTemperatureRecord psv:P6591 ?maximumTemperatureValue.
    ?maximumTemperatureValue wikibase:quantityAmount ?maximumTemperatureQuantity;
    wikibase:quantityUnit ?temperatureUnit.
    {
    ?countryItem rdfs:label ?Country.
    FILTER((LANG(?Country)) = "en")
    }
    {
    ?temperatureUnit wdt:P5061 ?unitSymbol.
    FILTER((LANG(?unitSymbol)) = "en")
    FILTER(CONTAINS(?unitSymbol, "C"))
    }
    BIND(CONCAT(STR(?maximumTemperatureQuantity), " ", ?unitSymbol) AS ?MaximumTemperature)
}
ORDER BY (?Country)    
    `

    body := getJson8(url, query)
    temperatures := getTemperatures(body)
    sort.Slice(temperatures, func(a, b int) bool {
        return temperatures[a].celsius > temperatures[b].celsius
    })
    table := formatTable8(temperatures)
    return table
}

func getJson8(url string, query string) []byte {
    client := http.Client{Timeout: time.Second * 2}
    request, err := http.NewRequest(http.MethodGet, url, nil)
    if err != nil {
		log.Fatal(err)
	}

    requestQuery := request.URL.Query()
    requestQuery.Add("query", query)
    requestQuery.Add("format", "json")
    request.URL.RawQuery = requestQuery.Encode()

    response, err := client.Do(request)
    if err != nil {
		log.Fatal(err)
	}

    if response.Body != nil {
		defer response.Body.Close()
	}

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}

    return body
}

func getTemperatures(body []byte) []Temperature8 {
    type Head struct {
        Vars []string `json:"vars"`
    }

    type Country struct {
        Value string `json:"Value"`
    }

    type MaximumTemperature struct {
        Value string `json:"Value"`
    }

    type Bindings struct {
        Country     Country             `json:"Country"`
        Temperature MaximumTemperature  `json:"MaximumTemperature"`
    }

    type Results struct {
        Bindings []Bindings `json:"bindings"` 
    }

    type Result struct {
        Head    Head    `json:"head"`
        Results Results `json:"results"`
    }

    var result Result	
    json.Unmarshal([]byte(body), &result)
 
    var temperatures []Temperature8
    for _, binding := range result.Results.Bindings {
        country := binding.Country.Value
        index := strings.Index(binding.Temperature.Value, " °C")
        celsius, _ := strconv.ParseFloat(binding.Temperature.Value[0:index], 64)
        fahrenheit := celsius * 9 / 5 + 32
    
        temperature := Temperature8 {
            country: country,
            celsius: celsius,
            fahrenheit: fahrenheit}

        temperatures  = append(temperatures, temperature)
    } 

    return temperatures
}

func formatTable8(temperatures []Temperature8) string {
    result := "<table><tr><th>Country</th>"
    result += "<th>Celsius</th><th>Fahrenheit</th></tr>"

    for _, temperature := range temperatures {
        result += "<tr><td>" + temperature.country + "</td>"
        result += "<td>" + strconv.FormatFloat(temperature.celsius, 'f', 1, 64) + "° C</td>"
        result += "<td>" + strconv.FormatFloat(temperature.fahrenheit, 'f', 1, 64) + "° F</td></tr>"
    }

    result += "</table>"
    return result
}

templates/lesson8.html[edit | edit source]

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Lesson 8</title>
    <link rel="stylesheet" href="styles.css">
    <style>
        p {
            min-height: 1em;
        }
    </style>
</head>

<body>
    <h1>Temperature Conversion</h1>
    <h2>Wikidata</h2>
    {{.Table}}
</body>

</html>

Try It[edit | edit source]

See Server-Side Scripting/Routes and Templates/Go to create a test environment.