1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-03-23 21:51:08 +02:00

Feature/#250 wait style (#255)

* Added support for parsed styles

* Added stdlib function.

* Added e2e tests

* Added e2e tests for STYLE_* functions
This commit is contained in:
Tim Voronov 2019-03-13 14:51:30 -04:00 committed by GitHub
parent 391836cbb2
commit 376ad77404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1225 additions and 217 deletions

9
Gopkg.lock generated
View File

@ -65,6 +65,14 @@
pruneopts = "UT" pruneopts = "UT"
revision = "d547d1d9531ed93dbdebcbff7f83e7c876a1e0ee" revision = "d547d1d9531ed93dbdebcbff7f83e7c876a1e0ee"
[[projects]]
digest = "1:fc51ecee8f31d03436c1a0167eb1e383ad0a241d02272541853f3995374a08f1"
name = "github.com/gorilla/css"
packages = ["scanner"]
pruneopts = "UT"
revision = "398b0b046082ecb3694c01bec6b336a06a4e530a"
version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d" digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
name = "github.com/gorilla/websocket" name = "github.com/gorilla/websocket"
@ -297,6 +305,7 @@
"github.com/corpix/uarand", "github.com/corpix/uarand",
"github.com/derekparker/trie", "github.com/derekparker/trie",
"github.com/gofrs/uuid", "github.com/gofrs/uuid",
"github.com/gorilla/css/scanner",
"github.com/labstack/echo", "github.com/labstack/echo",
"github.com/mafredri/cdp", "github.com/mafredri/cdp",
"github.com/mafredri/cdp/devtool", "github.com/mafredri/cdp/devtool",

View File

@ -45,6 +45,10 @@
name = "github.com/PuerkitoBio/goquery" name = "github.com/PuerkitoBio/goquery"
version = "1.5.0" version = "1.5.0"
[[constraint]]
name = "github.com/gorilla/css"
version = "v1.0.0"
[[constraint]] [[constraint]]
name = "github.com/gofrs/uuid" name = "github.com/gofrs/uuid"
version = "3.1.2" version = "3.1.2"

View File

@ -2,23 +2,52 @@ import random from "../../../utils/random.js";
const e = React.createElement; const e = React.createElement;
function render(id) { function render(id, props = {}) {
return e("span", { id: `${id}-content` }, ["Hello world"]); return e("span", { id: `${id}-content`, ...props }, ["Hello world"]);
} }
export default class AppearableComponent extends React.PureComponent { export default class AppearableComponent extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
let element = null;
if (props.appear) {
if (props.useStyle) {
element = render(props.id, { style: {display: "none"}})
}
} else {
if (props.useStyle) {
element = render(props.id, { style: {display: "block" }})
} else {
element = render(props.id)
}
}
this.state = { this.state = {
element: props.appear === true ? null : render(props.id) element
}; };
} }
handleClick() { handleClick() {
setTimeout(() => { setTimeout(() => {
const props = this.props;
let element = null;
if (props.appear) {
if (props.useStyle) {
element = render(props.id, { style: {display: "block" }})
} else {
element = render(props.id)
}
} else {
if (props.useStyle) {
element = render(props.id, { style: {display: "none"}})
}
}
this.setState({ this.setState({
element: this.props.appear === true ? render(this.props.id) : null element,
}) })
}, random()) }, random())
} }

View File

@ -48,13 +48,31 @@ export default class EventsPage extends React.Component {
title: "Appearable" title: "Appearable"
}) })
]), ]),
]),
e("div", { className: "row" }, [
e("div", { className: "col-lg-4"}, [ e("div", { className: "col-lg-4"}, [
e(Appearable, { e(Appearable, {
id: "wait-no-element", id: "wait-no-element",
appear: false, appear: false,
title: "Disappearable" title: "Disappearable"
}) })
]) ]),
e("div", { className: "col-lg-4"}, [
e(Appearable, {
id: "wait-style",
appear: true,
title: "Appearable with style",
useStyle: true,
})
]),
e("div", { className: "col-lg-4"}, [
e(Appearable, {
id: "wait-no-style",
appear: false,
title: "Disappearable",
useStyle: true,
})
]),
]) ])
]) ])
} }

View File

@ -0,0 +1,11 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET attrs = ATTR_GET(el, "style")
RETURN EXPECT("display: block;", attrs.style)

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.attributes.style
ATTR_REMOVE(el, "style")
WAIT(1000)
LET curr = el.attributes.style
RETURN prev == "display: block;" && curr == NONE ? "" : "expected attribute to be removed"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
ATTR_SET(el, "style", "color: black;")
WAIT(1000)
LET curr = el.style
RETURN curr.color == "black" ? "" : "styles should be updated"

View File

@ -0,0 +1,14 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
ATTR_SET(el, { style: "color: black;", "data-ferret-x": "test" })
WAIT(1000)
RETURN el.style.color == "black" && el.attributes["data-ferret-x"] == "test" ? "" : "styles should be updated"

View File

@ -0,0 +1,11 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET val = STYLE_GET(el, "display")
RETURN val.display == "block" ? "" : "could not get style values"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
STYLE_REMOVE(el, "display")
WAIT(1000)
LET curr = el.style
RETURN prev.display == "block" && curr.display == NONE ? "" : "expected style to be removed"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
STYLE_SET(el, "color", "black")
WAIT(1000)
LET curr = el.style
RETURN curr.color == "black" ? "" : "styles should be updated"

View File

@ -0,0 +1,17 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style
STYLE_SET(el, { color: "black", "min-width": "100px", "background-color": "#11111" })
WAIT(1000)
LET curr = el.style
RETURN curr.color == "black" && curr["min-width"] == "100px" && curr["background-color"] == "#11111" ? "" : "styles should be updated"

View File

@ -47,8 +47,8 @@ type (
innerHTML values.String innerHTML values.String
innerText *common.LazyValue innerText *common.LazyValue
value core.Value value core.Value
rawAttrs []string
attributes *common.LazyValue attributes *common.LazyValue
style *common.LazyValue
children []*HTMLElementIdentity children []*HTMLElementIdentity
loadedChildren *common.LazyValue loadedChildren *common.LazyValue
} }
@ -128,7 +128,6 @@ func LoadElement(
id, id,
node.Node.NodeType, node.Node.NodeType,
node.Node.NodeName, node.Node.NodeName,
node.Node.Attributes,
val, val,
innerHTML, innerHTML,
createChildrenArray(node.Node.Children), createChildrenArray(node.Node.Children),
@ -142,7 +141,6 @@ func NewHTMLElement(
id *HTMLElementIdentity, id *HTMLElementIdentity,
nodeType int, nodeType int,
nodeName string, nodeName string,
attributes []string,
value string, value string,
innerHTML values.String, innerHTML values.String,
children []*HTMLElementIdentity, children []*HTMLElementIdentity,
@ -157,8 +155,8 @@ func NewHTMLElement(
el.nodeName = values.NewString(nodeName) el.nodeName = values.NewString(nodeName)
el.innerHTML = innerHTML el.innerHTML = innerHTML
el.innerText = common.NewLazyValue(el.loadInnerText) el.innerText = common.NewLazyValue(el.loadInnerText)
el.rawAttrs = attributes
el.attributes = common.NewLazyValue(el.loadAttrs) el.attributes = common.NewLazyValue(el.loadAttrs)
el.style = common.NewLazyValue(el.parseStyle)
el.value = values.EmptyString el.value = values.EmptyString
el.loadedChildren = common.NewLazyValue(el.loadChildren) el.loadedChildren = common.NewLazyValue(el.loadChildren)
el.value = values.NewString(value) el.value = values.NewString(value)
@ -297,6 +295,99 @@ func (el *HTMLElement) Length() values.Int {
return values.NewInt(len(el.children)) return values.NewInt(len(el.children))
} }
func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
val, err := el.style.Read(ctx)
if err != nil {
return values.NewObject(), err
}
if val == values.None {
return values.NewObject(), nil
}
styles := val.(*values.Object)
return styles.Copy().(*values.Object), nil
}
func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
styles, err := el.style.Read(ctx)
if err != nil {
return values.None, err
}
val, found := styles.(*values.Object).Get(name)
if !found {
return values.None, nil
}
return val, nil
}
func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error {
if styles == nil {
return nil
}
val, err := el.style.Read(ctx)
if err != nil {
return err
}
currentStyles := val.(*values.Object)
styles.ForEach(func(value core.Value, key string) bool {
currentStyles.Set(values.NewString(key), value)
return true
})
str := common.SerializeStyles(ctx, currentStyles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) SetStyle(ctx context.Context, name values.String, value core.Value) error {
val, err := el.style.Read(ctx)
if err != nil {
return err
}
styles := val.(*values.Object)
styles.Set(name, value)
str := common.SerializeStyles(ctx, styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error {
if len(names) == 0 {
return nil
}
val, err := el.style.Read(ctx)
if err != nil {
return err
}
styles := val.(*values.Object)
for _, name := range names {
styles.Remove(name)
}
str := common.SerializeStyles(ctx, styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) GetAttributes(ctx context.Context) *values.Object { func (el *HTMLElement) GetAttributes(ctx context.Context) *values.Object {
val, err := el.attributes.Read(ctx) val, err := el.attributes.Read(ctx)
@ -326,6 +417,18 @@ func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) cor
return val return val
} }
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
var err error
attrs.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
return err == nil
})
return err
}
func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error { func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error {
return el.client.DOM.SetAttributeValue( return el.client.DOM.SetAttributeValue(
ctx, ctx,
@ -333,6 +436,21 @@ func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.Stri
) )
} }
func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.String) error {
for _, name := range names {
err := el.client.DOM.RemoveAttribute(
ctx,
dom.NewRemoveAttributeArgs(el.id.nodeID, name.String()),
)
if err != nil {
return err
}
}
return nil
}
func (el *HTMLElement) GetChildNodes(ctx context.Context) core.Value { func (el *HTMLElement) GetChildNodes(ctx context.Context) core.Value {
val, err := el.loadedChildren.Read(ctx) val, err := el.loadedChildren.Read(ctx)
@ -940,8 +1058,14 @@ func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
return parsed, nil return parsed, nil
} }
func (el *HTMLElement) loadAttrs(_ context.Context) (core.Value, error) { func (el *HTMLElement) loadAttrs(ctx context.Context) (core.Value, error) {
return parseAttrs(el.rawAttrs), nil repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.nodeID))
if err != nil {
return values.None, err
}
return parseAttrs(repl.Attributes), nil
} }
func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) { func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
@ -973,6 +1097,20 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
return loaded, nil return loaded, nil
} }
func (el *HTMLElement) parseStyle(ctx context.Context) (core.Value, error) {
value := el.GetAttribute(ctx, "style")
if value == values.None {
return values.NewObject(), nil
}
if value.Type() != types.String {
return values.NewObject(), nil
}
return common.DeserializeStyles(value.(values.String))
}
func (el *HTMLElement) handlePageReload(_ context.Context, _ interface{}) { func (el *HTMLElement) handlePageReload(_ context.Context, _ interface{}) {
el.Close() el.Close()
} }
@ -990,6 +1128,12 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return return
} }
// they are not event loaded
// just ignore the event
if !el.attributes.Ready() {
return
}
el.attributes.Write(ctx, func(v core.Value, err error) { el.attributes.Write(ctx, func(v core.Value, err error) {
if err != nil { if err != nil {
el.logError(err).Msg("failed to update element") el.logError(err).Msg("failed to update element")
@ -997,6 +1141,10 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return return
} }
if reply.Name == "style" {
el.style.Reset()
}
attrs, ok := v.(*values.Object) attrs, ok := v.(*values.Object)
if !ok { if !ok {
@ -1033,6 +1181,10 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
return return
} }
if reply.Name == "style" {
el.style.Reset()
}
attrs, ok := v.(*values.Object) attrs, ok := v.(*values.Object)
if !ok { if !ok {

View File

@ -4,10 +4,10 @@ import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"github.com/MontFerret/ferret/pkg/drivers"
"math" "math"
"strings" "strings"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/events" "github.com/MontFerret/ferret/pkg/drivers/cdp/events"
"github.com/MontFerret/ferret/pkg/drivers/common" "github.com/MontFerret/ferret/pkg/drivers/common"

View File

@ -59,6 +59,18 @@ func GetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
} }
return values.GetIn(ctx, attrs, path[1:]) return values.GetIn(ctx, attrs, path[1:])
case "style":
styles, err := el.GetStyles(ctx)
if err != nil {
return values.None, err
}
if len(path) == 1 {
return styles, nil
}
return values.GetIn(ctx, styles, path[1:])
default: default:
return GetInNode(ctx, el, path) return GetInNode(ctx, el, path)
} }

View File

@ -9,18 +9,18 @@ import (
) )
type ( type (
LazyFactory func(ctx context.Context) (core.Value, error) LazyValueFactory func(ctx context.Context) (core.Value, error)
LazyValue struct { LazyValue struct {
sync.Mutex sync.Mutex
factory LazyFactory factory LazyValueFactory
ready bool ready bool
value core.Value value core.Value
err error err error
} }
) )
func NewLazyValue(factory LazyFactory) *LazyValue { func NewLazyValue(factory LazyValueFactory) *LazyValue {
lz := new(LazyValue) lz := new(LazyValue)
lz.ready = false lz.ready = false
lz.factory = factory lz.factory = factory

View File

@ -54,6 +54,15 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
return err return err
} }
curr := el.GetAttributes(ctx)
// remove all previous attributes
err = el.RemoveAttribute(ctx, curr.Keys()...)
if err != nil {
return err
}
obj := value.(*values.Object) obj := value.(*values.Object)
obj.ForEach(func(value core.Value, key string) bool { obj.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String())) err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
@ -61,6 +70,35 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
return err == nil return err == nil
}) })
return err
case "style":
if len(path) > 1 {
attrName := path[1]
return el.SetStyle(ctx, values.NewString(attrName.String()), value)
}
err := core.ValidateType(value, types.Object)
if err != nil {
return err
}
styles, err := el.GetStyles(ctx)
if err != nil {
return err
}
err = el.RemoveStyle(ctx, styles.Keys()...)
obj := value.(*values.Object)
obj.ForEach(func(value core.Value, key string) bool {
err = el.SetStyle(ctx, values.NewString(key), value)
return err == nil
})
return err return err
case "value": case "value":
if len(path) > 1 { if len(path) > 1 {

View File

@ -0,0 +1,103 @@
package common
import (
"bytes"
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"strconv"
"strings"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/gorilla/css/scanner"
)
func DeserializeStyles(input values.String) (*values.Object, error) {
styles := values.NewObject()
if input == values.EmptyString {
return styles, nil
}
s := scanner.New(input.String())
var name string
var value bytes.Buffer
var setValue = func() {
styles.Set(values.NewString(strings.TrimSpace(name)), values.NewString(strings.TrimSpace(value.String())))
name = ""
value.Reset()
}
for {
token := s.Next()
if token == nil {
break
}
if token.Type == scanner.TokenEOF {
break
}
if name == "" && token.Type == scanner.TokenIdent {
name = token.Value
// skip : and white spaces
for {
token = s.Next()
if token.Value != ":" && token.Type != scanner.TokenS {
break
}
}
}
switch token.Type {
case scanner.TokenChar:
// end of style declaration
if token.Value == ";" {
if name != "" {
setValue()
}
} else {
value.WriteString(token.Value)
}
case scanner.TokenNumber:
num, err := strconv.ParseFloat(token.Value, 64)
if err == nil {
styles.Set(values.NewString(name), values.NewFloat(num))
// reset prop
name = ""
value.Reset()
}
default:
value.WriteString(token.Value)
}
}
if name != "" && value.Len() > 0 {
setValue()
}
return styles, nil
}
func SerializeStyles(_ context.Context, styles *values.Object) values.String {
if styles == nil {
return values.EmptyString
}
var b bytes.Buffer
styles.ForEach(func(value core.Value, key string) bool {
b.WriteString(key)
b.WriteString(": ")
b.WriteString(value.String())
b.WriteString("; ")
return true
})
return values.NewString(b.String())
}

View File

@ -0,0 +1,102 @@
package common_test
import (
"bytes"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
type style struct {
raw string
name values.String
value core.Value
}
func TestDeserializeStyles(t *testing.T) {
Convey("DeserializeStyles", t, func() {
styles := []style{
{
raw: "min-height: 1.15",
name: "min-height",
value: values.NewFloat(1.15),
},
{
raw: "background-color: #4A154B",
name: "background-color",
value: values.NewString("#4A154B"),
},
{
raw: "font-size:26pt",
name: "font-size",
value: values.NewString("26pt"),
},
{
raw: "page-break-after:avoid",
name: "page-break-after",
value: values.NewString("avoid"),
},
{
raw: `font-family: Arial,"Helvetica Neue",Helvetica,sans-serif`,
name: "font-family",
value: values.NewString(`Arial,"Helvetica Neue",Helvetica,sans-serif`),
},
{
raw: "color: black",
name: "color",
value: values.NewString("black"),
},
{
raw: "display: inline-block",
name: "display",
value: values.NewString("inline-block"),
},
{
raw: "min-width: 50",
name: "min-width",
value: values.NewFloat(50),
},
}
Convey("Should parse a single style", func() {
for _, s := range styles {
out, err := common.DeserializeStyles(values.NewString(s.raw))
So(err, ShouldBeNil)
So(out, ShouldNotBeNil)
value, exists := out.Get(s.name)
So(bool(exists), ShouldBeTrue)
So(value.Compare(s.value) == 0, ShouldBeTrue)
}
})
Convey("Should parse multiple styles", func() {
var buff bytes.Buffer
for _, s := range styles {
buff.WriteString(s.raw)
buff.WriteString("; ")
}
out, err := common.DeserializeStyles(values.NewString(buff.String()))
So(err, ShouldBeNil)
So(out, ShouldNotBeNil)
So(int(out.Length()), ShouldEqual, len(styles))
for _, s := range styles {
value, exists := out.Get(s.name)
So(bool(exists), ShouldBeTrue)
So(value.Compare(s.value) == 0, ShouldBeTrue)
}
})
})
}

View File

@ -16,6 +16,7 @@ import (
type HTMLElement struct { type HTMLElement struct {
selection *goquery.Selection selection *goquery.Selection
attrs *values.Object attrs *values.Object
styles *values.Object
children *values.Array children *values.Array
} }
@ -24,22 +25,22 @@ func NewHTMLElement(node *goquery.Selection) (drivers.HTMLElement, error) {
return nil, core.Error(core.ErrMissedArgument, "element selection") return nil, core.Error(core.ErrMissedArgument, "element selection")
} }
return &HTMLElement{node, nil, nil}, nil return &HTMLElement{node, nil, nil, nil}, nil
} }
func (nd *HTMLElement) MarshalJSON() ([]byte, error) { func (el *HTMLElement) MarshalJSON() ([]byte, error) {
return json.Marshal(nd.InnerText(context.Background()).String()) return json.Marshal(el.InnerText(context.Background()).String())
} }
func (nd *HTMLElement) Type() core.Type { func (el *HTMLElement) Type() core.Type {
return drivers.HTMLElementType return drivers.HTMLElementType
} }
func (nd *HTMLElement) String() string { func (el *HTMLElement) String() string {
return nd.InnerHTML(context.Background()).String() return el.InnerHTML(context.Background()).String()
} }
func (nd *HTMLElement) Compare(other core.Value) int64 { func (el *HTMLElement) Compare(other core.Value) int64 {
switch other.Type() { switch other.Type() {
case drivers.HTMLElementType: case drivers.HTMLElementType:
other := other.(drivers.HTMLElement) other := other.(drivers.HTMLElement)
@ -47,18 +48,18 @@ func (nd *HTMLElement) Compare(other core.Value) int64 {
ctx, fn := drivers.WithDefaultTimeout(context.Background()) ctx, fn := drivers.WithDefaultTimeout(context.Background())
defer fn() defer fn()
return nd.InnerHTML(ctx).Compare(other.InnerHTML(ctx)) return el.InnerHTML(ctx).Compare(other.InnerHTML(ctx))
default: default:
return drivers.Compare(nd.Type(), other.Type()) return drivers.Compare(el.Type(), other.Type())
} }
} }
func (nd *HTMLElement) Unwrap() interface{} { func (el *HTMLElement) Unwrap() interface{} {
return nd.selection return el.selection
} }
func (nd *HTMLElement) Hash() uint64 { func (el *HTMLElement) Hash() uint64 {
str, err := nd.selection.Html() str, err := el.selection.Html()
if err != nil { if err != nil {
return 0 return 0
@ -66,21 +67,21 @@ func (nd *HTMLElement) Hash() uint64 {
h := fnv.New64a() h := fnv.New64a()
h.Write([]byte(nd.Type().String())) h.Write([]byte(el.Type().String()))
h.Write([]byte(":")) h.Write([]byte(":"))
h.Write([]byte(str)) h.Write([]byte(str))
return h.Sum64() return h.Sum64()
} }
func (nd *HTMLElement) Copy() core.Value { func (el *HTMLElement) Copy() core.Value {
c, _ := NewHTMLElement(nd.selection.Clone()) c, _ := NewHTMLElement(el.selection.Clone())
return c return c
} }
func (nd *HTMLElement) NodeType() values.Int { func (el *HTMLElement) NodeType() values.Int {
nodes := nd.selection.Nodes nodes := el.selection.Nodes
if len(nodes) == 0 { if len(nodes) == 0 {
return 0 return 0
@ -89,24 +90,24 @@ func (nd *HTMLElement) NodeType() values.Int {
return values.NewInt(common.ToHTMLType(nodes[0].Type)) return values.NewInt(common.ToHTMLType(nodes[0].Type))
} }
func (nd *HTMLElement) Close() error { func (el *HTMLElement) Close() error {
return nil return nil
} }
func (nd *HTMLElement) NodeName() values.String { func (el *HTMLElement) NodeName() values.String {
return values.NewString(goquery.NodeName(nd.selection)) return values.NewString(goquery.NodeName(el.selection))
} }
func (nd *HTMLElement) Length() values.Int { func (el *HTMLElement) Length() values.Int {
if nd.children == nil { if el.children == nil {
nd.children = nd.parseChildren() el.children = el.parseChildren()
} }
return nd.children.Length() return el.children.Length()
} }
func (nd *HTMLElement) GetValue(_ context.Context) core.Value { func (el *HTMLElement) GetValue(_ context.Context) core.Value {
val, ok := nd.selection.Attr("value") val, ok := el.selection.Attr("value")
if ok { if ok {
return values.NewString(val) return values.NewString(val)
@ -115,18 +116,18 @@ func (nd *HTMLElement) GetValue(_ context.Context) core.Value {
return values.EmptyString return values.EmptyString
} }
func (nd *HTMLElement) SetValue(_ context.Context, value core.Value) error { func (el *HTMLElement) SetValue(_ context.Context, value core.Value) error {
nd.selection.SetAttr("value", value.String()) el.selection.SetAttr("value", value.String())
return nil return nil
} }
func (nd *HTMLElement) InnerText(_ context.Context) values.String { func (el *HTMLElement) InnerText(_ context.Context) values.String {
return values.NewString(nd.selection.Text()) return values.NewString(el.selection.Text())
} }
func (nd *HTMLElement) InnerHTML(_ context.Context) values.String { func (el *HTMLElement) InnerHTML(_ context.Context) values.String {
h, err := nd.selection.Html() h, err := el.selection.Html()
if err != nil { if err != nil {
return values.EmptyString return values.EmptyString
@ -135,48 +136,140 @@ func (nd *HTMLElement) InnerHTML(_ context.Context) values.String {
return values.NewString(h) return values.NewString(h)
} }
func (nd *HTMLElement) GetAttributes(_ context.Context) *values.Object { func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
if nd.attrs == nil { if err := el.ensureStyles(ctx); err != nil {
nd.attrs = nd.parseAttrs() return values.NewObject(), err
} }
return nd.attrs return el.styles.Copy().(*values.Object), nil
} }
func (nd *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value { func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
v, ok := nd.selection.Attr(name.String()) if err := el.ensureStyles(ctx); err != nil {
return values.None, err
if ok {
return values.NewString(v)
} }
return values.None return el.styles.MustGet(name), nil
} }
func (nd *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error { func (el *HTMLElement) SetStyle(ctx context.Context, name values.String, value core.Value) error {
nd.selection.SetAttr(string(name), string(value)) if err := el.ensureStyles(ctx); err != nil {
return err
}
el.styles.Set(name, value)
str := common.SerializeStyles(ctx, el.styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) SetStyles(ctx context.Context, newStyles *values.Object) error {
if newStyles == nil {
return nil
}
if err := el.ensureStyles(ctx); err != nil {
return err
}
newStyles.ForEach(func(i core.Value, key string) bool {
el.styles.Set(values.NewString(key), i)
return true
})
str := common.SerializeStyles(ctx, el.styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) RemoveStyle(ctx context.Context, name ...values.String) error {
if len(name) == 0 {
return nil
}
if err := el.ensureStyles(ctx); err != nil {
return err
}
for _, s := range name {
el.styles.Remove(s)
}
str := common.SerializeStyles(ctx, el.styles)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
if attrs == nil {
return nil
}
el.ensureAttrs()
var err error
attrs.ForEach(func(value core.Value, key string) bool {
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
return err == nil
})
return err
}
func (el *HTMLElement) GetAttributes(_ context.Context) *values.Object {
el.ensureAttrs()
return el.attrs.Copy().(*values.Object)
}
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
el.ensureAttrs()
return el.attrs.MustGet(name)
}
func (el *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
el.ensureAttrs()
el.attrs.Set(name, value)
el.selection.SetAttr(string(name), string(value))
return nil return nil
} }
func (nd *HTMLElement) GetChildNodes(_ context.Context) core.Value { func (el *HTMLElement) RemoveAttribute(_ context.Context, name ...values.String) error {
if nd.children == nil { el.ensureAttrs()
nd.children = nd.parseChildren()
for _, attr := range name {
el.attrs.Remove(attr)
el.selection.RemoveAttr(attr.String())
} }
return nd.children return nil
} }
func (nd *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value { func (el *HTMLElement) GetChildNodes(_ context.Context) core.Value {
if nd.children == nil { if el.children == nil {
nd.children = nd.parseChildren() el.children = el.parseChildren()
} }
return nd.children.Get(idx) return el.children
} }
func (nd *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value { func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
selection := nd.selection.Find(selector.String()) if el.children == nil {
el.children = el.parseChildren()
}
return el.children.Get(idx)
}
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
selection := el.selection.Find(selector.String())
if selection == nil { if selection == nil {
return values.None return values.None
@ -191,8 +284,8 @@ func (nd *HTMLElement) QuerySelector(_ context.Context, selector values.String)
return res return res
} }
func (nd *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value { func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
selection := nd.selection.Find(selector.String()) selection := el.selection.Find(selector.String())
if selection == nil { if selection == nil {
return values.None return values.None
@ -211,8 +304,8 @@ func (nd *HTMLElement) QuerySelectorAll(_ context.Context, selector values.Strin
return arr return arr
} }
func (nd *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.String) values.String { func (el *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.String) values.String {
selection := nd.selection.Find(selector.String()) selection := el.selection.Find(selector.String())
str, err := selection.Html() str, err := selection.Html()
@ -224,8 +317,8 @@ func (nd *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.St
return values.NewString(str) return values.NewString(str)
} }
func (nd *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values.String) *values.Array { func (el *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values.String) *values.Array {
selection := nd.selection.Find(selector.String()) selection := el.selection.Find(selector.String())
arr := values.NewArray(selection.Length()) arr := values.NewArray(selection.Length())
selection.Each(func(_ int, selection *goquery.Selection) { selection.Each(func(_ int, selection *goquery.Selection) {
@ -240,14 +333,14 @@ func (nd *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values
return arr return arr
} }
func (nd *HTMLElement) InnerTextBySelector(_ context.Context, selector values.String) values.String { func (el *HTMLElement) InnerTextBySelector(_ context.Context, selector values.String) values.String {
selection := nd.selection.Find(selector.String()) selection := el.selection.Find(selector.String())
return values.NewString(selection.Text()) return values.NewString(selection.Text())
} }
func (nd *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values.String) *values.Array { func (el *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values.String) *values.Array {
selection := nd.selection.Find(selector.String()) selection := el.selection.Find(selector.String())
arr := values.NewArray(selection.Length()) arr := values.NewArray(selection.Length())
selection.Each(func(_ int, selection *goquery.Selection) { selection.Each(func(_ int, selection *goquery.Selection) {
@ -257,8 +350,8 @@ func (nd *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values
return arr return arr
} }
func (nd *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int { func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
selection := nd.selection.Find(selector.String()) selection := el.selection.Find(selector.String())
if selection == nil { if selection == nil {
return values.ZeroInt return values.ZeroInt
@ -267,8 +360,8 @@ func (nd *HTMLElement) CountBySelector(_ context.Context, selector values.String
return values.NewInt(selection.Size()) return values.NewInt(selection.Size())
} }
func (nd *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) values.Boolean { func (el *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) values.Boolean {
selection := nd.selection.Closest(selector.String()) selection := el.selection.Closest(selector.String())
if selection == nil { if selection == nil {
return values.False return values.False
@ -277,47 +370,83 @@ func (nd *HTMLElement) ExistsBySelector(_ context.Context, selector values.Strin
return values.True return values.True
} }
func (nd *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) { func (el *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
return common.GetInElement(ctx, nd, path) return common.GetInElement(ctx, el, path)
} }
func (nd *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error { func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
return common.SetInElement(ctx, nd, path, value) return common.SetInElement(ctx, el, path, value)
} }
func (nd *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) { func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
return common.NewIterator(nd) return common.NewIterator(el)
} }
func (nd *HTMLElement) Click(_ context.Context) (values.Boolean, error) { func (el *HTMLElement) Click(_ context.Context) (values.Boolean, error) {
return false, core.ErrNotSupported return false, core.ErrNotSupported
} }
func (nd *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error { func (el *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error {
return core.ErrNotSupported return core.ErrNotSupported
} }
func (nd *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) { func (el *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported return nil, core.ErrNotSupported
} }
func (nd *HTMLElement) ScrollIntoView(_ context.Context) error { func (el *HTMLElement) ScrollIntoView(_ context.Context) error {
return core.ErrNotSupported return core.ErrNotSupported
} }
func (nd *HTMLElement) Hover(_ context.Context) error { func (el *HTMLElement) Hover(_ context.Context) error {
return core.ErrNotSupported return core.ErrNotSupported
} }
func (nd *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error { func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported return core.ErrNotSupported
} }
func (nd *HTMLElement) parseAttrs() *values.Object { func (el *HTMLElement) ensureStyles(ctx context.Context) error {
if el.styles == nil {
styles, err := el.parseStyles(ctx)
if err != nil {
return err
}
el.styles = styles
}
return nil
}
func (el *HTMLElement) parseStyles(ctx context.Context) (*values.Object, error) {
str := el.GetAttribute(ctx, "style")
if str == values.None {
return values.NewObject(), nil
}
styles, err := common.DeserializeStyles(values.NewString(str.String()))
if err != nil {
return nil, err
}
return styles, nil
}
func (el *HTMLElement) ensureAttrs() {
if el.attrs == nil {
el.attrs = el.parseAttrs()
}
}
func (el *HTMLElement) parseAttrs() *values.Object {
obj := values.NewObject() obj := values.NewObject()
for _, name := range common.Attributes { for _, name := range common.Attributes {
val, ok := nd.selection.Attr(name) val, ok := el.selection.Attr(name)
if ok { if ok {
obj.Set(values.NewString(name), values.NewString(val)) obj.Set(values.NewString(name), values.NewString(val))
@ -327,8 +456,8 @@ func (nd *HTMLElement) parseAttrs() *values.Object {
return obj return obj
} }
func (nd *HTMLElement) parseChildren() *values.Array { func (el *HTMLElement) parseChildren() *values.Array {
children := nd.selection.Children() children := el.selection.Children()
arr := values.NewArray(10) arr := values.NewArray(10)

View File

@ -53,12 +53,26 @@ type (
SetValue(ctx context.Context, value core.Value) error SetValue(ctx context.Context, value core.Value) error
GetStyles(ctx context.Context) (*values.Object, error)
GetStyle(ctx context.Context, name values.String) (core.Value, error)
SetStyles(ctx context.Context, values *values.Object) error
SetStyle(ctx context.Context, name values.String, value core.Value) error
RemoveStyle(ctx context.Context, name ...values.String) error
GetAttributes(ctx context.Context) *values.Object GetAttributes(ctx context.Context) *values.Object
GetAttribute(ctx context.Context, name values.String) core.Value GetAttribute(ctx context.Context, name values.String) core.Value
SetAttributes(ctx context.Context, values *values.Object) error
SetAttribute(ctx context.Context, name, value values.String) error SetAttribute(ctx context.Context, name, value values.String) error
RemoveAttribute(ctx context.Context, name ...values.String) error
InnerHTMLBySelector(ctx context.Context, selector values.String) values.String InnerHTMLBySelector(ctx context.Context, selector values.String) values.String
InnerHTMLBySelectorAll(ctx context.Context, selector values.String) *values.Array InnerHTMLBySelectorAll(ctx context.Context, selector values.String) *values.Array

View File

@ -22,7 +22,7 @@ type (
KeyedCollection interface { KeyedCollection interface {
core.Value core.Value
Measurable Measurable
Keys() []string Keys() []values.String
Get(key values.String) (core.Value, values.Boolean) Get(key values.String) (core.Value, values.Boolean)
Set(key values.String, value core.Value) Set(key values.String, value core.Value)
} }

View File

@ -10,7 +10,7 @@ type KeyedIterator struct {
valVar string valVar string
keyVar string keyVar string
values KeyedCollection values KeyedCollection
keys []string keys []values.String
pos int pos int
} }
@ -40,7 +40,7 @@ func (iterator *KeyedIterator) Next(_ context.Context, scope *core.Scope) (*core
} }
if len(iterator.keys) > iterator.pos { if len(iterator.keys) > iterator.pos {
key := values.NewString(iterator.keys[iterator.pos]) key := iterator.keys[iterator.pos]
val, _ := iterator.values.Get(key) val, _ := iterator.values.Get(key)
iterator.pos++ iterator.pos++

View File

@ -80,10 +80,23 @@ func (t *Object) Compare(other core.Value) int64 {
var res int64 var res int64
sortedT := sort.StringSlice(t.Keys()) tKeys := make([]string, 0, len(t.value))
for k := range t.value {
tKeys = append(tKeys, k)
}
sortedT := sort.StringSlice(tKeys)
sortedT.Sort() sortedT.Sort()
sortedOther := sort.StringSlice(other.Keys()) otherKeys := make([]string, 0, other.Length())
other.ForEach(func(value core.Value, k string) bool {
otherKeys = append(otherKeys, k)
return true
})
sortedOther := sort.StringSlice(otherKeys)
sortedOther.Sort() sortedOther.Sort()
var tVal, otherVal core.Value var tVal, otherVal core.Value
@ -178,11 +191,21 @@ func (t *Object) Length() Int {
return Int(len(t.value)) return Int(len(t.value))
} }
func (t *Object) Keys() []string { func (t *Object) Keys() []String {
keys := make([]string, 0, len(t.value)) keys := make([]String, 0, len(t.value))
for k := range t.value { for k := range t.value {
keys = append(keys, k) keys = append(keys, NewString(k))
}
return keys
}
func (t *Object) Values() []core.Value {
keys := make([]core.Value, 0, len(t.value))
for _, v := range t.value {
keys = append(keys, v)
} }
return keys return keys

View File

@ -21,7 +21,6 @@ func NewLib() map[string]core.Function {
"REMOVE_NTH": RemoveNth, "REMOVE_NTH": RemoveNth,
"REMOVE_VALUE": RemoveValue, "REMOVE_VALUE": RemoveValue,
"REMOVE_VALUES": RemoveValues, "REMOVE_VALUES": RemoveValues,
"REVERSE": Reverse,
"SHIFT": Shift, "SHIFT": Shift,
"SLICE": Slice, "SLICE": Slice,
"SORTED": Sorted, "SORTED": Sorted,

View File

@ -1,36 +0,0 @@
package arrays
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Reverse return a new array with its elements reversed.
// @param array (Array) - Target array.
// @returns (Array) - A new array with its elements reversed.
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], types.Array)
if err != nil {
return values.None, err
}
arr := args[0].(*values.Array)
size := int(arr.Length())
result := values.NewArray(size)
for i := size - 1; i >= 0; i-- {
result.Push(arr.Get(values.NewInt(i)))
}
return result, nil
}

View File

@ -4,6 +4,7 @@ import "github.com/MontFerret/ferret/pkg/runtime/core"
func NewLib() map[string]core.Function { func NewLib() map[string]core.Function {
return map[string]core.Function{ return map[string]core.Function{
"LENGTH": Length, "LENGTH": Length,
"REVERSE": Reverse,
} }
} }

View File

@ -0,0 +1,51 @@
package collections
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Reverse returns the reverse of a given string or array value.
// @param text (String|Array) - The string or array to reverse.
// @returns (String|Array) - Returns a reversed version of a given value.
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
err = core.ValidateType(args[0], types.Array, types.String)
if err != nil {
return values.None, err
}
switch col := args[0].(type) {
case values.String:
runes := []rune(string(col))
size := len(runes)
// Reverse
for i := 0; i < size/2; i++ {
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
}
return values.NewString(string(runes)), nil
case *values.Array:
size := int(col.Length())
result := values.NewArray(size)
for i := size - 1; i >= 0; i-- {
result.Push(col.Get(values.NewInt(i)))
}
return result, nil
default:
return values.None, nil
}
}

View File

@ -1,14 +1,33 @@
package arrays_test package collections_test
import ( import (
"context" "context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
. "github.com/smartystreets/goconvey/convey"
"testing" "testing"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/collections"
. "github.com/smartystreets/goconvey/convey"
) )
func TestReverse(t *testing.T) { func TestReverse(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = collections.Reverse(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should reverse a text with right encoding", t, func() {
out, _ := collections.Reverse(
context.Background(),
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
)
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
})
Convey("Should return a copy of an array with reversed elements", t, func() { Convey("Should return a copy of an array with reversed elements", t, func() {
arr := values.NewArrayWith( arr := values.NewArrayWith(
values.NewInt(1), values.NewInt(1),
@ -19,7 +38,7 @@ func TestReverse(t *testing.T) {
values.NewInt(6), values.NewInt(6),
) )
out, err := arrays.Reverse( out, err := collections.Reverse(
context.Background(), context.Background(),
arr, arr,
) )
@ -31,7 +50,7 @@ func TestReverse(t *testing.T) {
Convey("Should return an empty array when there no elements in a source one", t, func() { Convey("Should return an empty array when there no elements in a source one", t, func() {
arr := values.NewArray(0) arr := values.NewArray(0)
out, err := arrays.Reverse( out, err := collections.Reverse(
context.Background(), context.Background(),
arr, arr,
) )

View File

@ -0,0 +1,41 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// AttributeGet gets single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Attribute name(s).
// @returns Object - Key-value pairs of attribute values.
func AttributeGet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
names := args[1:]
result := values.NewObject()
attrs := el.GetAttributes(ctx)
for _, n := range names {
name := values.NewString(n.String())
val, exists := attrs.Get(name)
if exists {
result.Set(name, val)
}
}
return result, nil
}

View File

@ -0,0 +1,41 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeRemove removes single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Attribute name(s).
func AttributeRemove(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
attrs := args[1:]
attrsStr := make([]values.String, 0, len(attrs))
for _, attr := range attrs {
str, ok := attr.(values.String)
if !ok {
return values.None, core.TypeError(attr.Type(), types.String)
}
attrsStr = append(attrsStr, str)
}
return values.None, el.RemoveAttribute(ctx, attrsStr...)
}

View File

@ -0,0 +1,50 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeSet sets or updates a single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param nameOrObj (String | Object) - Attribute name or an object representing a key-value pair of attributes.
// @param value (String) - If a second parameter is a string value, this parameter represent an attribute value.
func AttributeSet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
switch arg1 := args[1].(type) {
case values.String:
// ATTR_SET(el, name, value)
err = core.ValidateArgs(args, 3, 3)
if err != nil {
return values.None, nil
}
arg2, ok := args[2].(values.String)
if !ok {
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
}
return values.None, el.SetAttribute(ctx, arg1, arg2)
case *values.Object:
// ATTR_SET(el, values)
return values.None, el.SetAttributes(ctx, arg1)
default:
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
}
}

View File

@ -14,6 +14,9 @@ const defaultTimeout = 5000
func NewLib() map[string]core.Function { func NewLib() map[string]core.Function {
return map[string]core.Function{ return map[string]core.Function{
"ATTR_GET": AttributeGet,
"ATTR_REMOVE": AttributeRemove,
"ATTR_SET": AttributeSet,
"CLICK": Click, "CLICK": Click,
"CLICK_ALL": ClickAll, "CLICK_ALL": ClickAll,
"DOCUMENT": Document, "DOCUMENT": Document,
@ -40,6 +43,9 @@ func NewLib() map[string]core.Function {
"SCROLL_ELEMENT": ScrollInto, "SCROLL_ELEMENT": ScrollInto,
"SCROLL_TOP": ScrollTop, "SCROLL_TOP": ScrollTop,
"SELECT": Select, "SELECT": Select,
"STYLE_GET": StyleGet,
"STYLE_REMOVE": StyleRemove,
"STYLE_SET": StyleSet,
"WAIT_ELEMENT": WaitElement, "WAIT_ELEMENT": WaitElement,
"WAIT_NO_ELEMENT": WaitNoElement, "WAIT_NO_ELEMENT": WaitNoElement,
"WAIT_CLASS": WaitClass, "WAIT_CLASS": WaitClass,

View File

@ -0,0 +1,44 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// StyleGet gets single or more style attribute value(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Style name(s).
// @returns Object - Key-value pairs of style values.
func StyleGet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
names := args[1:]
result := values.NewObject()
for _, n := range names {
name := values.NewString(n.String())
val, err := el.GetStyle(ctx, name)
if err != nil {
return values.None, err
}
if val != values.None {
result.Set(name, val)
}
}
return result, nil
}

View File

@ -0,0 +1,41 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// StyleRemove removes single or more style attribute value(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Style name(s).
func StyleRemove(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
attrs := args[1:]
attrsStr := make([]values.String, 0, len(attrs))
for _, attr := range attrs {
str, ok := attr.(values.String)
if !ok {
return values.None, core.TypeError(attr.Type(), types.String)
}
attrsStr = append(attrsStr, str)
}
return values.None, el.RemoveStyle(ctx, attrsStr...)
}

View File

@ -0,0 +1,44 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// StyleSet sets or updates a single or more style attribute value of a given element.
// @param el (HTMLElement) - Target element.
// @param nameOrObj (String | Object) - Style name or an object representing a key-value pair of attributes.
// @param value (String) - If a second parameter is a string value, this parameter represent a style value.
func StyleSet(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, core.MaxArgs)
if err != nil {
return values.None, err
}
el, err := resolveElement(args[0])
if err != nil {
return values.None, err
}
switch arg1 := args[1].(type) {
case values.String:
// STYLE_SET(el, name, value)
err = core.ValidateArgs(args, 3, 3)
if err != nil {
return values.None, nil
}
return values.None, el.SetStyle(ctx, arg1, args[2])
case *values.Object:
// STYLE_SET(el, values)
return values.None, el.SetStyles(ctx, arg1)
default:
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
}
}

View File

@ -1,6 +1,7 @@
package stdlib package stdlib
import ( import (
"fmt"
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/stdlib/arrays" "github.com/MontFerret/ferret/pkg/stdlib/arrays"
"github.com/MontFerret/ferret/pkg/stdlib/collections" "github.com/MontFerret/ferret/pkg/stdlib/collections"
@ -17,6 +18,10 @@ func NewLib() map[string]core.Function {
add := func(l map[string]core.Function) { add := func(l map[string]core.Function) {
for name, fn := range l { for name, fn := range l {
if _, exists := lib[name]; exists {
panic(fmt.Sprintf("%s function already exists", name))
}
lib[name] = fn lib[name] = fn
} }
} }

View File

@ -185,15 +185,15 @@ func isEqualObjects(obj1 *values.Object, obj2 *values.Object) bool {
var val2 core.Value var val2 core.Value
for _, key := range obj1.Keys() { for _, key := range obj1.Keys() {
val1, _ = obj1.Get(values.NewString(key)) val1, _ = obj1.Get(key)
val2, _ = obj2.Get(values.NewString(key)) val2, _ = obj2.Get(key)
if val1.Compare(val2) != 0 { if val1.Compare(val2) != 0 {
return false return false
} }
} }
for _, key := range obj2.Keys() { for _, key := range obj2.Keys() {
val1, _ = obj1.Get(values.NewString(key)) val1, _ = obj1.Get(key)
val2, _ = obj2.Get(values.NewString(key)) val2, _ = obj2.Get(key)
if val2.Compare(val1) != 0 { if val2.Compare(val1) != 0 {
return false return false
} }

View File

@ -36,7 +36,15 @@ func Keys(_ context.Context, args ...core.Value) (core.Value, error) {
needSort = bool(args[1].(values.Boolean)) needSort = bool(args[1].(values.Boolean))
} }
keys := sort.StringSlice(obj.Keys()) oKeys := make([]string, 0, obj.Length())
obj.ForEach(func(value core.Value, key string) bool {
oKeys = append(oKeys, key)
return true
})
keys := sort.StringSlice(oKeys)
keysArray := values.NewArray(len(keys)) keysArray := values.NewArray(len(keys))
if needSort { if needSort {

View File

@ -22,7 +22,6 @@ func NewLib() map[string]core.Function {
"REGEXP_SPLIT": RegexSplit, "REGEXP_SPLIT": RegexSplit,
"REGEXP_TEST": RegexTest, "REGEXP_TEST": RegexTest,
"REGEXP_REPLACE": RegexReplace, "REGEXP_REPLACE": RegexReplace,
"REVERSE": Reverse,
"RIGHT": Right, "RIGHT": Right,
"RTRIM": RTrim, "RTRIM": RTrim,
"SHA1": Sha1, "SHA1": Sha1,

View File

@ -1,30 +0,0 @@
package strings
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Reverse returns the reverse of the string value.
// @param text (String) - The string to revers
// @returns (String) - Returns a reversed version of the string.
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.EmptyString, err
}
text := args[0].String()
runes := []rune(text)
size := len(runes)
// Reverse
for i := 0; i < size/2; i++ {
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
}
return values.NewString(string(runes)), nil
}

View File

@ -1,29 +0,0 @@
package strings_test
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/stdlib/strings"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestReverse(t *testing.T) {
Convey("When args are not passed", t, func() {
Convey("It should return an error", func() {
var err error
_, err = strings.Reverse(context.Background())
So(err, ShouldBeError)
})
})
Convey("Should reverse a text with right encoding", t, func() {
out, _ := strings.Reverse(
context.Background(),
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
)
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
})
}