2018-09-18 22:42:38 +02:00
|
|
|
package values
|
|
|
|
|
|
|
|
import (
|
2019-02-13 19:31:18 +02:00
|
|
|
"context"
|
2018-10-28 07:45:26 +02:00
|
|
|
"encoding/binary"
|
2018-09-26 01:04:07 +02:00
|
|
|
"encoding/json"
|
2018-10-28 07:45:26 +02:00
|
|
|
"hash/fnv"
|
2018-09-29 03:04:16 +02:00
|
|
|
"reflect"
|
2018-10-28 07:45:26 +02:00
|
|
|
"sort"
|
2018-09-18 22:42:38 +02:00
|
|
|
"time"
|
2018-10-12 17:58:08 +02:00
|
|
|
|
|
|
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
2019-02-13 19:31:18 +02:00
|
|
|
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
2018-09-18 22:42:38 +02:00
|
|
|
)
|
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
func GetIn(ctx context.Context, from core.Value, byPath []core.Value) (core.Value, error) {
|
2018-09-18 22:42:38 +02:00
|
|
|
if byPath == nil || len(byPath) == 0 {
|
|
|
|
return None, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var result = from
|
|
|
|
var err error
|
|
|
|
|
2018-12-22 06:14:41 +02:00
|
|
|
for i, segment := range byPath {
|
2018-09-18 22:42:38 +02:00
|
|
|
if result == None || result == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
segmentType := segment.Type()
|
|
|
|
|
|
|
|
switch result.Type() {
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Object:
|
2018-09-18 22:42:38 +02:00
|
|
|
obj := result.(*Object)
|
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
if segmentType != types.String {
|
|
|
|
return nil, core.TypeError(segmentType, types.String)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result, _ = obj.Get(segment.(String))
|
|
|
|
|
|
|
|
break
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Array:
|
2018-09-18 22:42:38 +02:00
|
|
|
arr := result.(*Array)
|
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
if segmentType != types.Int {
|
|
|
|
return nil, core.TypeError(segmentType, types.Int)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result = arr.Get(segment.(Int))
|
|
|
|
|
|
|
|
break
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.HTMLElement, types.HTMLDocument:
|
2018-10-06 00:30:03 +02:00
|
|
|
el := result.(HTMLNode)
|
2018-09-18 22:42:38 +02:00
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
if segmentType == types.Int {
|
2018-09-18 22:42:38 +02:00
|
|
|
result = el.GetChildNode(segment.(Int))
|
2019-02-13 19:31:18 +02:00
|
|
|
} else if segmentType == types.String {
|
2018-09-18 22:42:38 +02:00
|
|
|
strSegment := segment.(String)
|
|
|
|
|
|
|
|
switch strSegment {
|
|
|
|
case "nodeType":
|
|
|
|
result = el.NodeType()
|
|
|
|
case "nodeName":
|
|
|
|
result = el.NodeName()
|
|
|
|
case "innerText":
|
|
|
|
result = el.InnerText()
|
2018-10-17 01:33:02 +02:00
|
|
|
case "innerHTML":
|
2018-10-06 01:40:09 +02:00
|
|
|
result = el.InnerHTML()
|
2018-11-28 14:39:12 +02:00
|
|
|
case "value":
|
2018-09-18 22:42:38 +02:00
|
|
|
result = el.Value()
|
|
|
|
case "attributes":
|
|
|
|
result = el.GetAttributes()
|
|
|
|
case "children":
|
|
|
|
result = el.GetChildNodes()
|
|
|
|
case "length":
|
|
|
|
result = el.Length()
|
2018-09-28 01:05:56 +02:00
|
|
|
case "url":
|
2019-02-13 19:31:18 +02:00
|
|
|
if result.Type() == types.HTMLDocument {
|
2018-10-06 00:30:03 +02:00
|
|
|
doc, ok := result.(HTMLDocument)
|
2018-09-28 01:05:56 +02:00
|
|
|
|
|
|
|
if ok {
|
2018-10-06 01:40:09 +02:00
|
|
|
result = doc.URL()
|
2018-09-28 01:05:56 +02:00
|
|
|
}
|
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
default:
|
|
|
|
result = None
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return None, err
|
|
|
|
}
|
|
|
|
} else {
|
2019-02-13 19:31:18 +02:00
|
|
|
return nil, core.TypeError(segmentType, types.Int, types.String)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2018-12-22 06:14:41 +02:00
|
|
|
getter, ok := result.(core.Getter)
|
|
|
|
|
|
|
|
if ok {
|
2019-02-13 19:31:18 +02:00
|
|
|
return getter.GetIn(ctx, byPath[i:])
|
2018-12-22 06:14:41 +02:00
|
|
|
}
|
|
|
|
|
2018-09-18 22:42:38 +02:00
|
|
|
return None, core.TypeError(
|
|
|
|
from.Type(),
|
2019-02-13 19:31:18 +02:00
|
|
|
types.Array,
|
|
|
|
types.Object,
|
|
|
|
types.HTMLDocument,
|
|
|
|
types.HTMLElement,
|
2018-09-18 22:42:38 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
func SetIn(ctx context.Context, to core.Value, byPath []core.Value, value core.Value) error {
|
2018-09-18 22:42:38 +02:00
|
|
|
if byPath == nil || len(byPath) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var parent core.Value
|
|
|
|
var current = to
|
|
|
|
target := len(byPath) - 1
|
|
|
|
|
|
|
|
for idx, segment := range byPath {
|
|
|
|
parent = current
|
|
|
|
isTarget := target == idx
|
|
|
|
segmentType := segment.Type()
|
|
|
|
|
|
|
|
switch parent.Type() {
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Object:
|
2018-09-18 22:42:38 +02:00
|
|
|
parent := parent.(*Object)
|
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
if segmentType != types.String {
|
|
|
|
return core.TypeError(segmentType, types.String)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if isTarget == false {
|
|
|
|
current, _ = parent.Get(segment.(String))
|
|
|
|
} else {
|
|
|
|
parent.Set(segment.(String), value)
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Array:
|
|
|
|
if segmentType != types.Int {
|
|
|
|
return core.TypeError(segmentType, types.Int)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
parent := parent.(*Array)
|
|
|
|
|
|
|
|
if isTarget == false {
|
|
|
|
current = parent.Get(segment.(Int))
|
|
|
|
} else {
|
2018-12-22 06:14:41 +02:00
|
|
|
if err := parent.Set(segment.(Int), value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
default:
|
2018-12-22 06:14:41 +02:00
|
|
|
setter, ok := parent.(core.Setter)
|
|
|
|
|
|
|
|
if ok {
|
2019-02-13 19:31:18 +02:00
|
|
|
return setter.SetIn(ctx, byPath[idx:], value)
|
2018-12-22 06:14:41 +02:00
|
|
|
}
|
|
|
|
|
2018-09-18 22:42:38 +02:00
|
|
|
// redefine parent
|
2019-02-13 19:31:18 +02:00
|
|
|
isArray := segmentType == types.Int
|
2018-09-18 22:42:38 +02:00
|
|
|
|
|
|
|
// it's not an index
|
|
|
|
if isArray == false {
|
|
|
|
obj := NewObject()
|
|
|
|
parent = obj
|
|
|
|
|
2019-02-13 19:31:18 +02:00
|
|
|
if segmentType != types.String {
|
|
|
|
return core.TypeError(segmentType, types.String)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if isTarget {
|
|
|
|
obj.Set(segment.(String), value)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
arr := NewArray(10)
|
|
|
|
parent = arr
|
|
|
|
|
|
|
|
if isTarget {
|
2018-12-22 06:14:41 +02:00
|
|
|
if err := arr.Set(segment.(Int), value); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// set new parent
|
2019-02-13 19:31:18 +02:00
|
|
|
if err := SetIn(ctx, to, byPath[0:idx-1], parent); err != nil {
|
2018-12-22 06:14:41 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
|
|
|
|
if isTarget == false {
|
|
|
|
current = None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func Parse(input interface{}) core.Value {
|
2019-02-15 14:41:08 +02:00
|
|
|
switch value := input.(type) {
|
2018-09-18 22:42:38 +02:00
|
|
|
case bool:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewBoolean(value)
|
2018-09-18 22:42:38 +02:00
|
|
|
case string:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewString(value)
|
2018-09-18 22:42:38 +02:00
|
|
|
case int:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewInt(value)
|
2018-09-18 22:42:38 +02:00
|
|
|
case float64:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewFloat(value)
|
2018-09-18 22:42:38 +02:00
|
|
|
case float32:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewFloat(float64(value))
|
2018-09-18 22:42:38 +02:00
|
|
|
case time.Time:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewDateTime(value)
|
2018-09-18 22:42:38 +02:00
|
|
|
case []interface{}:
|
2019-02-15 14:41:08 +02:00
|
|
|
arr := NewArray(len(value))
|
2018-09-18 22:42:38 +02:00
|
|
|
|
2019-02-15 14:41:08 +02:00
|
|
|
for _, el := range value {
|
2018-09-18 22:42:38 +02:00
|
|
|
arr.Push(Parse(el))
|
|
|
|
}
|
|
|
|
|
|
|
|
return arr
|
|
|
|
case map[string]interface{}:
|
|
|
|
obj := NewObject()
|
|
|
|
|
2019-02-15 14:41:08 +02:00
|
|
|
for key, el := range value {
|
2018-09-18 22:42:38 +02:00
|
|
|
obj.Set(NewString(key), Parse(el))
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
case []byte:
|
2019-02-15 14:41:08 +02:00
|
|
|
return NewBinary(value)
|
2018-10-04 16:38:42 +02:00
|
|
|
case nil:
|
|
|
|
return None
|
2018-09-29 03:04:16 +02:00
|
|
|
default:
|
2019-02-15 14:41:08 +02:00
|
|
|
v := reflect.ValueOf(value)
|
|
|
|
t := reflect.TypeOf(value)
|
2018-09-29 03:04:16 +02:00
|
|
|
kind := t.Kind()
|
|
|
|
|
|
|
|
if kind == reflect.Slice || kind == reflect.Array {
|
|
|
|
size := v.Len()
|
|
|
|
arr := NewArray(size)
|
|
|
|
|
2018-10-05 23:01:54 +02:00
|
|
|
for i := 0; i < size; i++ {
|
2019-02-15 14:41:08 +02:00
|
|
|
curVal := v.Index(i)
|
|
|
|
arr.Push(Parse(curVal.Interface()))
|
2018-09-29 03:04:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return arr
|
|
|
|
}
|
|
|
|
|
|
|
|
if kind == reflect.Map {
|
|
|
|
keys := v.MapKeys()
|
|
|
|
obj := NewObject()
|
|
|
|
|
|
|
|
for _, k := range keys {
|
|
|
|
key := Parse(k.Interface())
|
2019-02-15 14:41:08 +02:00
|
|
|
curVal := v.MapIndex(k)
|
2018-09-29 03:04:16 +02:00
|
|
|
|
2019-02-15 14:41:08 +02:00
|
|
|
obj.Set(NewString(key.String()), Parse(curVal.Interface()))
|
2018-09-29 03:04:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
|
|
|
if kind == reflect.Struct {
|
|
|
|
obj := NewObject()
|
|
|
|
size := t.NumField()
|
|
|
|
|
2018-10-05 23:01:54 +02:00
|
|
|
for i := 0; i < size; i++ {
|
2018-09-29 03:04:16 +02:00
|
|
|
field := t.Field(i)
|
2019-02-15 14:41:08 +02:00
|
|
|
fieldValue := v.Field(i)
|
2018-09-18 22:42:38 +02:00
|
|
|
|
2019-02-15 14:41:08 +02:00
|
|
|
obj.Set(NewString(field.Name), Parse(fieldValue.Interface()))
|
2018-09-29 03:04:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
|
|
|
return None
|
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
2018-09-23 02:28:33 +02:00
|
|
|
|
2018-09-26 01:04:07 +02:00
|
|
|
func Unmarshal(value json.RawMessage) (core.Value, error) {
|
|
|
|
var o interface{}
|
|
|
|
|
|
|
|
err := json.Unmarshal(value, &o)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return None, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return Parse(o), nil
|
|
|
|
}
|
|
|
|
|
2018-10-25 03:30:05 +02:00
|
|
|
func IsCloneable(value core.Value) Boolean {
|
|
|
|
switch value.Type() {
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Array:
|
2018-10-25 03:30:05 +02:00
|
|
|
return NewBoolean(true)
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Object:
|
2018-10-25 03:30:05 +02:00
|
|
|
return NewBoolean(true)
|
|
|
|
default:
|
|
|
|
return NewBoolean(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 02:28:33 +02:00
|
|
|
func ToBoolean(input core.Value) core.Value {
|
|
|
|
switch input.Type() {
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Boolean:
|
2018-09-23 02:28:33 +02:00
|
|
|
return input
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.None:
|
2018-09-23 02:28:33 +02:00
|
|
|
return False
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.String:
|
2018-09-23 02:28:33 +02:00
|
|
|
return NewBoolean(input.String() != "")
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Int:
|
2018-09-23 02:28:33 +02:00
|
|
|
return NewBoolean(input.(Int) != 0)
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Float:
|
2018-09-23 02:28:33 +02:00
|
|
|
return NewBoolean(input.(Float) != 0)
|
|
|
|
default:
|
|
|
|
return True
|
|
|
|
}
|
|
|
|
}
|
2018-10-12 17:58:08 +02:00
|
|
|
|
2018-10-25 03:30:05 +02:00
|
|
|
func ToArray(input core.Value) core.Value {
|
|
|
|
switch input.Type() {
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Boolean,
|
|
|
|
types.Int,
|
|
|
|
types.Float,
|
|
|
|
types.String,
|
|
|
|
types.DateTime:
|
2018-10-25 03:30:05 +02:00
|
|
|
|
|
|
|
return NewArrayWith(input)
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.HTMLElement,
|
|
|
|
types.HTMLDocument:
|
2018-10-25 03:30:05 +02:00
|
|
|
val := input.(HTMLNode)
|
|
|
|
attrs := val.GetAttributes()
|
|
|
|
|
|
|
|
obj, ok := attrs.(*Object)
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
return NewArray(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
arr := NewArray(int(obj.Length()))
|
|
|
|
|
|
|
|
obj.ForEach(func(value core.Value, key string) bool {
|
|
|
|
arr.Push(value)
|
|
|
|
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
return obj
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Array:
|
2018-10-25 03:30:05 +02:00
|
|
|
return input.Copy()
|
2019-02-13 19:31:18 +02:00
|
|
|
case types.Object:
|
2018-10-25 03:30:05 +02:00
|
|
|
obj, ok := input.(*Object)
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
return NewArray(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
arr := NewArray(int(obj.Length()))
|
|
|
|
|
|
|
|
obj.ForEach(func(value core.Value, key string) bool {
|
|
|
|
arr.Push(value)
|
|
|
|
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
return obj
|
2018-10-12 17:58:08 +02:00
|
|
|
default:
|
2018-10-25 03:30:05 +02:00
|
|
|
return NewArray(0)
|
2018-10-12 17:58:08 +02:00
|
|
|
}
|
|
|
|
}
|
2018-10-28 07:45:26 +02:00
|
|
|
|
|
|
|
func MapHash(input map[string]core.Value) uint64 {
|
|
|
|
h := fnv.New64a()
|
|
|
|
|
|
|
|
keys := make([]string, 0, len(input))
|
|
|
|
|
|
|
|
for key := range input {
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// order does not really matter
|
|
|
|
// but it will give us a consistent hash sum
|
|
|
|
sort.Strings(keys)
|
|
|
|
endIndex := len(keys) - 1
|
|
|
|
|
|
|
|
h.Write([]byte("{"))
|
|
|
|
|
|
|
|
for idx, key := range keys {
|
|
|
|
h.Write([]byte(key))
|
|
|
|
h.Write([]byte(":"))
|
|
|
|
|
|
|
|
el := input[key]
|
|
|
|
|
|
|
|
bytes := make([]byte, 8)
|
|
|
|
binary.LittleEndian.PutUint64(bytes, el.Hash())
|
|
|
|
|
|
|
|
h.Write(bytes)
|
|
|
|
|
|
|
|
if idx != endIndex {
|
|
|
|
h.Write([]byte(","))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
h.Write([]byte("}"))
|
|
|
|
|
|
|
|
return h.Sum64()
|
|
|
|
}
|