status-go/vendor/github.com/keighl/metabolize/metabolize.go

154 lines
2.7 KiB
Go

package metabolize
import (
"fmt"
"golang.org/x/net/html"
"io"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
const (
TagName = `meta`
htmlRegion = `head`
htmlTag = `meta`
)
var (
NotStructError = fmt.Errorf(`Destination is not a struct`)
)
type MetaData map[string]string
type Meta struct {
Title string `meta:og:title`
Desc string `meta:og:image`
}
func Metabolize(doc io.Reader, obj interface{}) error {
data, err := ParseDocument(doc)
if err != nil {
return err
}
return Decode(data, obj)
}
func Decode(data MetaData, obj interface{}) error {
elem := reflect.ValueOf(obj).Elem()
if elem.Kind() != reflect.Struct {
return NotStructError
}
for i := 0; i < elem.NumField(); i++ {
field := elem.Type().Field(i)
fieldValue := elem.FieldByName(field.Name)
if !fieldValue.IsValid() {
continue
}
if !fieldValue.CanSet() {
continue
}
tag := field.Tag.Get(TagName)
if tag == "" {
continue
}
tags := strings.Split(tag, ",")
for _, tagItem := range tags {
if data[tagItem] == "" {
continue
}
if fieldValue.Kind() == reflect.String {
val := string(data[tagItem])
fieldValue.SetString(val)
}
if fieldValue.Kind() == reflect.Bool {
val, err := strconv.ParseBool(data[tagItem])
if err != nil {
continue
}
fieldValue.SetBool(val)
}
if fieldValue.Kind() == reflect.Float64 {
val, err := strconv.ParseFloat(data[tagItem], 64)
if err != nil {
continue
}
fieldValue.SetFloat(val)
}
if fieldValue.Kind() == reflect.Int64 {
val, err := strconv.ParseInt(data[tagItem], 0, 64)
if err != nil {
continue
}
fieldValue.SetInt(val)
}
if field.Type.Name() == "URL" {
val, err := url.Parse(data[tagItem])
if err != nil {
continue
}
fieldValue.Set(reflect.ValueOf(*val))
}
if field.Type.Name() == "Time" {
val, err := time.Parse(time.RFC3339, data[tagItem])
if err != nil {
continue
}
fieldValue.Set(reflect.ValueOf(val))
}
}
}
return nil
}
func ParseDocument(doc io.Reader) (MetaData, error) {
data := MetaData{}
tokenizer := html.NewTokenizer(doc)
for {
tt := tokenizer.Next()
if tt == html.ErrorToken {
if tokenizer.Err() == io.EOF {
return data, nil
}
return nil, tokenizer.Err()
}
token := tokenizer.Token()
if token.Type == html.EndTagToken && token.Data == htmlRegion {
return data, nil
}
if token.Data == htmlTag {
var property, content string
for _, attr := range token.Attr {
switch attr.Key {
case "property", "name":
property = strings.ToLower(attr.Val)
case "content":
content = attr.Val
}
}
if property != "" {
data[strings.TrimSpace(property)] = content
}
}
}
return data, nil
}