1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-11-06 08:39:09 +02:00

pull master

This commit is contained in:
Владимир Фетисов
2019-10-03 22:42:14 +03:00
134 changed files with 5000 additions and 2135 deletions

View File

@@ -1,19 +1,20 @@
package compiler
import (
"github.com/pkg/errors"
"github.com/MontFerret/ferret/pkg/parser"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/stdlib"
"github.com/pkg/errors"
)
type FqlCompiler struct {
type Compiler struct {
*NamespaceContainer
}
func New(setters ...Option) *FqlCompiler {
c := &FqlCompiler{}
func New(setters ...Option) *Compiler {
c := &Compiler{}
c.NamespaceContainer = newRootNamespace()
c.funcs = make(map[string]core.Function)
@@ -32,7 +33,7 @@ func New(setters ...Option) *FqlCompiler {
return c
}
func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error) {
func (c *Compiler) Compile(query string) (program *runtime.Program, err error) {
if query == "" {
return nil, ErrEmptyQuery
}
@@ -69,7 +70,7 @@ func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error
return program, err
}
func (c *FqlCompiler) MustCompile(query string) *runtime.Program {
func (c *Compiler) MustCompile(query string) *runtime.Program {
program, err := c.Compile(query)
if err != nil {

View File

@@ -119,14 +119,110 @@ func TestMember(t *testing.T) {
So(string(out), ShouldEqual, `"wsx"`)
})
Convey("Deep path", func() {
c := compiler.New()
p, err := c.Compile(`
LET obj = {
first: {
second: {
third: {
fourth: {
fifth: {
bottom: true
}
}
}
}
}
}
RETURN obj.first.second.third.fourth.fifth.bottom
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `true`)
})
Convey("Deep computed path", func() {
c := compiler.New()
p, err := c.Compile(`
LET obj = {
first: {
second: {
third: {
fourth: {
fifth: {
bottom: true
}
}
}
}
}
}
RETURN obj["first"]["second"]["third"]["fourth"]["fifth"].bottom
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `true`)
})
Convey("Prop after a func call", func() {
c := compiler.New()
p, err := c.Compile(`
LET arr = [{ name: "Bob" }]
RETURN FIRST(arr).name
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `"Bob"`)
})
Convey("Computed prop after a func call", func() {
c := compiler.New()
p, err := c.Compile(`
LET arr = [{ name: { first: "Bob" } }]
RETURN FIRST(arr)['name'].first
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `"Bob"`)
})
})
}
func BenchmarkMemberArray(b *testing.B) {
p := compiler.New().MustCompile(`
LET arr = [1]
LET arr = [[[[1]]]]
RETURN arr[0]
RETURN arr[0][0][0][0]
`)
for n := 0; n < b.N; n++ {
@@ -136,9 +232,21 @@ func BenchmarkMemberArray(b *testing.B) {
func BenchmarkMemberObject(b *testing.B) {
p := compiler.New().MustCompile(`
LET obj = { "foo": "bar"}
LET obj = {
first: {
second: {
third: {
fourth: {
fifth: {
bottom: true
}
}
}
}
}
}
RETURN obj.foo
RETURN obj.first.second.third.fourth.fifth.bottom
`)
for n := 0; n < b.N; n++ {

View File

@@ -63,4 +63,54 @@ func TestParam(t *testing.T) {
So(string(out), ShouldEqual, `[1,2,3,4]`)
})
Convey("Should be possible to use in member expression", t, func() {
prog := compiler.New().
MustCompile(`
RETURN @param.value
`)
out := prog.MustRun(
context.Background(),
runtime.WithParam("param", map[string]interface{}{
"value": "foobar",
}),
)
So(string(out), ShouldEqual, `"foobar"`)
})
Convey("Should be possible to use in member expression as a computed property", t, func() {
prog := compiler.New().
MustCompile(`
LET obj = { foo: "bar" }
RETURN obj[@param]
`)
out := prog.MustRun(
context.Background(),
runtime.WithParam("param", "foo"),
)
So(string(out), ShouldEqual, `"bar"`)
})
Convey("Should be possible to use in member expression as segments", t, func() {
prog := compiler.New().
MustCompile(`
LET doc = { foo: { bar: "baz" } }
RETURN doc.@attr.@subattr
`)
out := prog.MustRun(
context.Background(),
runtime.WithParam("attr", "foo"),
runtime.WithParam("subattr", "bar"),
)
So(string(out), ShouldEqual, `"baz"`)
})
}

View File

@@ -23,7 +23,7 @@ BAR
So(string(out), ShouldEqual, `"\nFOO\nBAR\n"`)
})
Convey("Should be possible to use multi line string with nested strings", t, func() {
Convey("Should be possible to use multi line string with nested strings using backtick", t, func() {
compiler.New().
MustCompile(fmt.Sprintf(`
RETURN %s<!DOCTYPE html>
@@ -54,6 +54,38 @@ RETURN %s<!DOCTYPE html>
So(string(out), ShouldEqual, string(out))
})
Convey("Should be possible to use multi line string with nested strings using tick", t, func() {
compiler.New().
MustCompile(fmt.Sprintf(`
RETURN %s<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GetTitle</title>
</head>
<body>
Hello world
</body>
</html>%s
`, "´", "´")).
MustRun(context.Background())
out, err := json.Marshal(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GetTitle</title>
</head>
<body>
Hello world
</body>
</html>`)
So(err, ShouldBeNil)
So(string(out), ShouldEqual, string(out))
})
}
func BenchmarkStringLiteral(b *testing.B) {

View File

@@ -736,15 +736,13 @@ func (v *visitor) doVisitForExpressionStatement(ctx *fql.ForExpressionStatementC
}
func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scope *scope) (core.Expression, error) {
varName := ctx.Identifier().GetText()
_, err := scope.GetVariable(varName)
member, err := v.doVisitMember(ctx.Member().(*fql.MemberContext), scope)
if err != nil {
return nil, err
}
children := ctx.GetChildren()
children := ctx.MemberPath().GetChildren()
path := make([]core.Expression, 0, len(children))
for _, child := range children {
@@ -784,9 +782,9 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
path = append(path, exp)
}
member, err := expressions.NewMemberExpression(
exp, err := expressions.NewMemberExpression(
v.getSourceMap(ctx),
varName,
member,
path,
)
@@ -794,7 +792,51 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
return nil, err
}
return member, nil
return exp, nil
}
func (v *visitor) doVisitMember(ctx *fql.MemberContext, scope *scope) (core.Expression, error) {
identifier := ctx.Identifier()
if identifier != nil {
varName := ctx.Identifier().GetText()
_, err := scope.GetVariable(varName)
if err != nil {
return nil, err
}
exp, err := expressions.NewVariableExpression(v.getSourceMap(ctx), varName)
if err != nil {
return nil, err
}
return exp, nil
}
fnCall := ctx.FunctionCallExpression()
if fnCall != nil {
exp, err := v.doVisitFunctionCallExpression(fnCall.(*fql.FunctionCallExpressionContext), scope)
if err != nil {
return nil, err
}
return exp, nil
}
param := ctx.Param()
exp, err := v.doVisitParamContext(param.(*fql.ParamContext), scope)
if err != nil {
return nil, err
}
return exp, nil
}
func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *scope) (core.Expression, error) {
@@ -847,7 +889,7 @@ func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *sco
return literals.NewObjectLiteralWith(props...), nil
}
func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, _ *scope) (core.Expression, error) {
func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, scope *scope) (core.Expression, error) {
var name string
identifier := ctx.Identifier()
@@ -860,6 +902,14 @@ func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, _ *sc
if stringLiteral != nil {
runes := []rune(stringLiteral.GetText())
name = string(runes[1 : len(runes)-1])
} else {
param, err := v.doVisitParamContext(ctx.Param().(*fql.ParamContext), scope)
if err != nil {
return nil, err
}
return param, nil
}
}
@@ -937,8 +987,6 @@ func (v *visitor) doVisitStringLiteral(ctx *fql.StringLiteralContext) (core.Expr
if strLiteral != nil {
text = strLiteral.GetText()
} else {
text = ctx.TemplateStringLiteral().GetText()
}
// remove extra quotes

View File

@@ -243,23 +243,23 @@ func (doc *HTMLDocument) GetNodeName() values.String {
return "#document"
}
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) core.Value {
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) (*values.Array, error) {
return doc.element.GetChildNodes(ctx)
}
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) core.Value {
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
return doc.element.GetChildNode(ctx, idx)
}
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
return doc.element.QuerySelector(ctx, selector)
}
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
return doc.element.QuerySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) values.Int {
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
return doc.element.CountBySelector(ctx, selector)
}
@@ -268,7 +268,7 @@ func (doc *HTMLDocument) ExistsBySelector(ctx context.Context, selector values.S
}
func (doc *HTMLDocument) GetTitle() values.String {
value, err := doc.exec.ReadProperty(context.Background(), doc.element.id.objectID, "title")
value, err := doc.exec.ReadProperty(context.Background(), doc.element.id.ObjectID, "title")
if err != nil {
doc.logError(errors.Wrap(err, "failed to read document title"))
@@ -317,32 +317,8 @@ func (doc *HTMLDocument) GetURL() values.String {
return values.NewString(doc.frames.Frame.URL)
}
func (doc *HTMLDocument) ClickBySelector(ctx context.Context, selector values.String) error {
return doc.element.ClickBySelector(ctx, selector)
}
func (doc *HTMLDocument) ClickBySelectorAll(ctx context.Context, selector values.String) error {
return doc.element.ClickBySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return doc.input.TypeBySelector(ctx, doc.element.id.nodeID, selector, value, delay)
}
func (doc *HTMLDocument) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) {
return doc.input.SelectBySelector(ctx, doc.element.id.nodeID, selector, value)
}
func (doc *HTMLDocument) FocusBySelector(ctx context.Context, selector values.String) error {
return doc.input.FocusBySelector(ctx, doc.element.id.nodeID, selector)
}
func (doc *HTMLDocument) MoveMouseBySelector(ctx context.Context, selector values.String) error {
return doc.input.MoveMouseBySelector(ctx, doc.element.id.nodeID, selector)
}
func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) error {
return doc.input.MoveMouseByXY(ctx, x, y)
return doc.input.MoveMouseByXY(ctx, float64(x), float64(y))
}
func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error {
@@ -501,11 +477,11 @@ func (doc *HTMLDocument) ScrollBottom(ctx context.Context) error {
}
func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector values.String) error {
return doc.input.ScrollIntoViewBySelector(ctx, selector)
return doc.input.ScrollIntoViewBySelector(ctx, selector.String())
}
func (doc *HTMLDocument) ScrollByXY(ctx context.Context, x, y values.Float) error {
return doc.input.ScrollByXY(ctx, x, y)
return doc.input.ScrollByXY(ctx, float64(x), float64(y))
}
func (doc *HTMLDocument) loadChildren(ctx context.Context) (value core.Value, e error) {

View File

@@ -2,15 +2,13 @@ package cdp
import (
"context"
"sync"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/devtool"
"github.com/mafredri/cdp/protocol/target"
"github.com/mafredri/cdp/rpcc"
"github.com/mafredri/cdp/session"
"github.com/pkg/errors"
"sync"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/logging"
@@ -18,7 +16,6 @@ import (
const DriverName = "cdp"
const BlankPageURL = "about:blank"
const DefaultTimeout = 5000 * time.Millisecond
var defaultViewport = &drivers.Viewport{
Width: 1600,
@@ -107,6 +104,34 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
params.Viewport = defaultViewport
}
if drv.options.Headers != nil && params.Headers == nil {
params.Headers = make(drivers.HTTPHeaders)
}
// set default headers
for k, v := range drv.options.Headers {
_, exists := params.Headers[k]
// do not override user's set values
if !exists {
params.Headers[k] = v
}
}
if drv.options.Cookies != nil && params.Cookies == nil {
params.Cookies = make(drivers.HTTPCookies)
}
// set default cookies
for k, v := range drv.options.Cookies {
_, exists := params.Cookies[k]
// do not override user's set values
if !exists {
params.Cookies[k] = v
}
}
return LoadHTMLPage(ctx, conn, params)
}

View File

@@ -8,6 +8,14 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
@@ -18,21 +26,14 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/html"
)
var emptyNodeID = dom.NodeID(0)
type (
HTMLElementIdentity struct {
nodeID dom.NodeID
objectID runtime.RemoteObjectID
NodeID dom.NodeID
ObjectID runtime.RemoteObjectID
}
HTMLElement struct {
@@ -48,7 +49,6 @@ type (
nodeName values.String
innerHTML *common.LazyValue
innerText *common.LazyValue
value core.Value
attributes *common.LazyValue
style *common.LazyValue
children []HTMLElementIdentity
@@ -82,10 +82,6 @@ func LoadHTMLElement(
return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %d", nodeID))
}
id := HTMLElementIdentity{}
id.nodeID = nodeID
id.objectID = *obj.Object.ObjectID
return LoadHTMLElementWithID(
ctx,
logger,
@@ -93,7 +89,10 @@ func LoadHTMLElement(
broker,
input,
exec,
id,
HTMLElementIdentity{
NodeID: nodeID,
ObjectID: *obj.Object.ObjectID,
},
)
}
@@ -110,18 +109,12 @@ func LoadHTMLElementWithID(
ctx,
dom.
NewDescribeNodeArgs().
SetObjectID(id.objectID).
SetObjectID(id.ObjectID).
SetDepth(1),
)
if err != nil {
return nil, core.Error(err, strconv.Itoa(int(id.nodeID)))
}
var val string
if node.Node.Value != nil {
val = *node.Node.Value
return nil, core.Error(err, strconv.Itoa(int(id.NodeID)))
}
return NewHTMLElement(
@@ -133,7 +126,6 @@ func LoadHTMLElementWithID(
id,
node.Node.NodeType,
node.Node.NodeName,
val,
createChildrenArray(node.Node.Children),
), nil
}
@@ -147,7 +139,6 @@ func NewHTMLElement(
id HTMLElementIdentity,
nodeType int,
nodeName string,
value string,
children []HTMLElementIdentity,
) *HTMLElement {
el := new(HTMLElement)
@@ -164,9 +155,7 @@ func NewHTMLElement(
el.innerText = common.NewLazyValue(el.loadInnerText)
el.attributes = common.NewLazyValue(el.loadAttrs)
el.style = common.NewLazyValue(el.parseStyle)
el.value = values.EmptyString
el.loadedChildren = common.NewLazyValue(el.loadChildren)
el.value = values.NewString(value)
el.children = children
broker.AddEventListener(events.EventReload, el.handlePageReload)
@@ -208,7 +197,7 @@ func (el *HTMLElement) MarshalJSON() ([]byte, error) {
}
func (el *HTMLElement) String() string {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(drivers.DefaultWaitTimeout)*time.Millisecond)
defer cancel()
res, err := el.GetInnerHTML(ctx)
@@ -242,7 +231,7 @@ func (el *HTMLElement) Hash() uint64 {
h.Write([]byte(el.Type().String()))
h.Write([]byte(":"))
h.Write([]byte(strconv.Itoa(int(el.id.nodeID))))
h.Write([]byte(strconv.Itoa(int(el.id.NodeID))))
return h.Sum64()
}
@@ -263,31 +252,20 @@ func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.
return common.SetInElement(ctx, el, path, value)
}
func (el *HTMLElement) GetValue(ctx context.Context) core.Value {
func (el *HTMLElement) GetValue(ctx context.Context) (core.Value, error) {
if el.IsDetached() {
return el.value
return values.None, drivers.ErrDetached
}
val, err := el.exec.ReadProperty(ctx, el.id.objectID, "value")
if err != nil {
el.logError(err).Msg("failed to get node value")
return el.value
}
el.value = val
return val
return el.exec.ReadProperty(ctx, el.id.ObjectID, "value")
}
func (el *HTMLElement) SetValue(ctx context.Context, value core.Value) error {
if el.IsDetached() {
// TODO: Return an error
return nil
return drivers.ErrDetached
}
return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.nodeID, value.String()))
return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.NodeID, value.String()))
}
func (el *HTMLElement) GetNodeType() values.Int {
@@ -395,33 +373,33 @@ func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) GetAttributes(ctx context.Context) *values.Object {
func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error) {
val, err := el.attributes.Read(ctx)
if err != nil {
return values.NewObject()
return values.NewObject(), err
}
attrs := val.(*values.Object)
// returning shallow copy
return attrs.Copy().(*values.Object)
return attrs.Copy().(*values.Object), nil
}
func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) core.Value {
func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) (core.Value, error) {
attrs, err := el.attributes.Read(ctx)
if err != nil {
return values.None
return values.None, err
}
val, found := attrs.(*values.Object).Get(name)
if !found {
return values.None
return values.None, nil
}
return val
return val, nil
}
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
@@ -439,7 +417,7 @@ func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object)
func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error {
return el.client.DOM.SetAttributeValue(
ctx,
dom.NewSetAttributeValueArgs(el.id.nodeID, string(name), string(value)),
dom.NewSetAttributeValueArgs(el.id.NodeID, string(name), string(value)),
)
}
@@ -447,7 +425,7 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri
for _, name := range names {
err := el.client.DOM.RemoveAttribute(
ctx,
dom.NewRemoveAttributeArgs(el.id.nodeID, name.String()),
dom.NewRemoveAttributeArgs(el.id.NodeID, name.String()),
)
if err != nil {
@@ -458,79 +436,61 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri
return nil
}
func (el *HTMLElement) GetChildNodes(ctx context.Context) core.Value {
func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) {
val, err := el.loadedChildren.Read(ctx)
if err != nil {
return values.NewArray(0)
return values.NewArray(0), err
}
return val
return val.Copy().(*values.Array), nil
}
func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) core.Value {
func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
val, err := el.loadedChildren.Read(ctx)
if err != nil {
return values.None
return values.None, err
}
return val.(*values.Array).Get(idx)
return val.(*values.Array).Get(idx), nil
}
func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
if el.IsDetached() {
return values.None
return values.None, drivers.ErrDetached
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String())
found, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve a node by selector")
return values.None
return values.None, err
}
if found.NodeID == emptyNodeID {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to find a node by selector. returned 0 NodeID")
return values.None
return values.None, nil
}
res, err := LoadHTMLElement(ctx, el.logger, el.client, el.events, el.input, el.exec, found.NodeID)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to load a child node by selector")
return values.None
return values.None, nil
}
return res
return res, nil
}
func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
if el.IsDetached() {
return values.NewArray(0)
return values.NewArray(0), drivers.ErrDetached
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve nodes by selector")
return values.None
return values.NewArray(0), err
}
arr := values.NewArray(len(res.NodeIDs))
@@ -547,10 +507,6 @@ func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.Str
childEl, err := LoadHTMLElement(ctx, el.logger, el.client, el.events, el.input, el.exec, id)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to load nodes by selector")
// close elements that are already loaded, but won't be used because of the error
if arr.Length() > 0 {
arr.ForEach(func(e core.Value, _ int) bool {
@@ -560,13 +516,13 @@ func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.Str
})
}
return values.None
return values.NewArray(0), err
}
arr.Push(childEl)
}
return arr
return arr, nil
}
func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (result core.Value, err error) {
@@ -578,7 +534,7 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res
out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.XPath(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: json.RawMessage(exp),
@@ -657,8 +613,8 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res
el.input,
el.exec,
HTMLElementIdentity{
nodeID: repl.NodeID,
objectID: *descr.Value.ObjectID,
NodeID: repl.NodeID,
ObjectID: *descr.Value.ObjectID,
},
)
@@ -689,8 +645,8 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res
el.input,
el.exec,
HTMLElementIdentity{
nodeID: repl.NodeID,
objectID: *out.ObjectID,
NodeID: repl.NodeID,
ObjectID: *out.ObjectID,
},
)
default:
@@ -737,7 +693,7 @@ func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector valu
ctx,
templates.GetInnerTextBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@@ -772,7 +728,7 @@ func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, inn
ctx,
templates.SetInnerTextBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@@ -798,7 +754,7 @@ func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector v
ctx,
templates.GetInnerTextBySelectorAll(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@@ -857,7 +813,7 @@ func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector valu
ctx,
templates.GetInnerHTMLBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@@ -892,7 +848,7 @@ func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, inn
ctx,
templates.SetInnerHTMLBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@@ -918,7 +874,7 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v
ctx,
templates.GetInnerHTMLBySelectorAll(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@@ -938,24 +894,19 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v
return arr, nil
}
func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) values.Int {
func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
if el.IsDetached() {
return values.ZeroInt
return values.ZeroInt, drivers.ErrDetached
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve nodes by selector")
return values.ZeroInt
return values.ZeroInt, err
}
return values.NewInt(len(res.NodeIDs))
return values.NewInt(len(res.NodeIDs)), nil
}
func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) {
@@ -964,7 +915,7 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil {
@@ -981,7 +932,11 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str
func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, when drivers.WaitEvent) error {
task := events.NewWaitTask(
func(ctx2 context.Context) (core.Value, error) {
current := el.GetAttribute(ctx2, "class")
current, err := el.GetAttribute(ctx2, "class")
if err != nil {
return values.None, nil
}
if current.Type() != types.String {
return values.None, nil
@@ -1033,7 +988,7 @@ func (el *HTMLElement) WaitForAttribute(
when drivers.WaitEvent,
) error {
task := events.NewValueWaitTask(when, value, func(ctx context.Context) (core.Value, error) {
return el.GetAttribute(ctx, name), nil
return el.GetAttribute(ctx, name)
}, events.DefaultPolling)
_, err := task.Run(ctx)
@@ -1051,16 +1006,16 @@ func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, val
return err
}
func (el *HTMLElement) Click(ctx context.Context) error {
return el.input.Click(ctx, el.id.objectID)
func (el *HTMLElement) Click(ctx context.Context, count values.Int) error {
return el.input.Click(ctx, el.id.ObjectID, int(count))
}
func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String) error {
return el.input.ClickBySelector(ctx, el.id.nodeID, selector)
func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String, count values.Int) error {
return el.input.ClickBySelector(ctx, el.id.NodeID, selector.String(), int(count))
}
func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String) error {
return el.input.ClickBySelectorAll(ctx, el.id.nodeID, selector)
func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error {
return el.input.ClickBySelectorAll(ctx, el.id.NodeID, selector.String(), int(count))
}
func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values.Int) error {
@@ -1068,23 +1023,63 @@ func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values
return core.Error(core.ErrInvalidOperation, "element is not an <input> element.")
}
return el.input.Type(ctx, el.id.objectID, value, delay)
return el.input.Type(ctx, el.id.ObjectID, input.TypeParams{
Text: value.String(),
Clear: false,
Delay: time.Duration(delay) * time.Millisecond,
})
}
func (el *HTMLElement) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return el.input.TypeBySelector(ctx, el.id.NodeID, selector.String(), input.TypeParams{
Text: value.String(),
Clear: false,
Delay: time.Duration(delay) * time.Millisecond,
})
}
func (el *HTMLElement) Clear(ctx context.Context) error {
return el.input.Clear(ctx, el.id.ObjectID)
}
func (el *HTMLElement) ClearBySelector(ctx context.Context, selector values.String) error {
return el.input.ClearBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values.Array, error) {
return el.input.Select(ctx, el.id.objectID, value)
return el.input.Select(ctx, el.id.ObjectID, value)
}
func (el *HTMLElement) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) {
return el.input.SelectBySelector(ctx, el.id.NodeID, selector.String(), value)
}
func (el *HTMLElement) ScrollIntoView(ctx context.Context) error {
return el.input.ScrollIntoView(ctx, el.id.objectID)
return el.input.ScrollIntoView(ctx, el.id.ObjectID)
}
func (el *HTMLElement) Focus(ctx context.Context) error {
return el.input.Focus(ctx, el.id.objectID)
return el.input.Focus(ctx, el.id.ObjectID)
}
func (el *HTMLElement) FocusBySelector(ctx context.Context, selector values.String) error {
return el.input.FocusBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) Blur(ctx context.Context) error {
return el.input.Blur(ctx, el.id.ObjectID)
}
func (el *HTMLElement) BlurBySelector(ctx context.Context, selector values.String) error {
return el.input.BlurBySelector(ctx, el.id.ObjectID, selector.String())
}
func (el *HTMLElement) Hover(ctx context.Context) error {
return el.input.MoveMouse(ctx, el.id.objectID)
return el.input.MoveMouse(ctx, el.id.ObjectID)
}
func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.String) error {
return el.input.MoveMouseBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) IsDetached() values.Boolean {
@@ -1139,7 +1134,7 @@ func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
}
func (el *HTMLElement) loadAttrs(ctx context.Context) (core.Value, error) {
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.nodeID))
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID))
if err != nil {
return values.None, err
@@ -1163,7 +1158,7 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
el.events,
el.input,
el.exec,
childID.nodeID,
childID.NodeID,
)
if err != nil {
@@ -1179,7 +1174,11 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
}
func (el *HTMLElement) parseStyle(ctx context.Context) (core.Value, error) {
value := el.GetAttribute(ctx, "style")
value, err := el.GetAttribute(ctx, "style")
if err != nil {
return values.None, err
}
if value == values.None {
return values.NewObject(), nil
@@ -1205,7 +1204,7 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
}
// it's not for this el
if reply.NodeID != el.id.nodeID {
if reply.NodeID != el.id.NodeID {
return
}
@@ -1215,6 +1214,10 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return
}
if el.IsDetached() {
return
}
el.attributes.Mutate(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@@ -1245,7 +1248,7 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
}
// it's not for this el
if reply.NodeID != el.id.nodeID {
if reply.NodeID != el.id.NodeID {
return
}
@@ -1255,6 +1258,10 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
return
}
if el.IsDetached() {
return
}
el.attributes.Mutate(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@@ -1283,13 +1290,20 @@ func (el *HTMLElement) handleChildrenCountChanged(ctx context.Context, message i
return
}
if reply.NodeID != el.id.nodeID {
if reply.NodeID != el.id.NodeID {
return
}
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
node, err := el.client.DOM.DescribeNode(
ctx,
dom.NewDescribeNodeArgs().SetObjectID(el.id.objectID),
dom.NewDescribeNodeArgs().SetObjectID(el.id.ObjectID),
)
if err != nil {
@@ -1298,9 +1312,6 @@ func (el *HTMLElement) handleChildrenCountChanged(ctx context.Context, message i
return
}
el.mu.Lock()
defer el.mu.Unlock()
el.children = createChildrenArray(node.Node.Children)
}
@@ -1311,7 +1322,7 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
return
}
if reply.ParentNodeID != el.id.nodeID {
if reply.ParentNodeID != el.id.NodeID {
return
}
@@ -1319,11 +1330,15 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
prevID := reply.PreviousNodeID
nextID := reply.Node.NodeID
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
for idx, id := range el.children {
if id.nodeID == prevID {
if id.NodeID == prevID {
targetIDx = idx
break
}
@@ -1334,7 +1349,7 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
}
nextIdentity := HTMLElementIdentity{
nodeID: reply.Node.NodeID,
NodeID: reply.Node.NodeID,
}
arr := el.children
@@ -1368,18 +1383,22 @@ func (el *HTMLElement) handleChildRemoved(ctx context.Context, message interface
return
}
if reply.ParentNodeID != el.id.nodeID {
if reply.ParentNodeID != el.id.NodeID {
return
}
targetIDx := -1
targetID := reply.NodeID
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
for idx, id := range el.children {
if id.nodeID == targetID {
if id.NodeID == targetID {
targetIDx = idx
break
}
@@ -1401,7 +1420,7 @@ func (el *HTMLElement) handleChildRemoved(ctx context.Context, message interface
el.logger.Error().
Timestamp().
Err(err).
Int("nodeID", int(el.id.nodeID)).
Int("nodeID", int(el.id.NodeID)).
Msg("failed to update element")
return
@@ -1419,7 +1438,7 @@ func (el *HTMLElement) logError(err error) *zerolog.Event {
return el.logger.
Error().
Timestamp().
Int("nodeID", int(el.id.nodeID)).
Str("objectID", string(el.id.objectID)).
Int("nodeID", int(el.id.NodeID)).
Str("objectID", string(el.id.ObjectID)).
Err(err)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"reflect"
"sync"
"time"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/page"
@@ -289,7 +290,7 @@ func (broker *EventBroker) emit(ctx context.Context, event Event, message interf
case <-ctx.Done():
return
default:
ctx2, fn := drivers.WithDefaultTimeout(ctx)
ctx2, fn := context.WithTimeout(ctx, time.Duration(drivers.DefaultTimeout)*time.Millisecond)
listener(ctx2, message)

View File

@@ -71,10 +71,10 @@ func parseAttrs(attrs []string) *values.Object {
func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, innerHTML values.String) error {
var objID *runtime.RemoteObjectID
if id.objectID != "" {
objID = &id.objectID
if id.ObjectID != "" {
objID = &id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return err
@@ -110,10 +110,10 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC
if nodeType != html.DocumentNode {
var objID runtime.RemoteObjectID
if id.objectID != "" {
objID = id.objectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return "", err
@@ -147,10 +147,10 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC
func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, innerText values.String) error {
var objID *runtime.RemoteObjectID
if id.objectID != "" {
objID = &id.objectID
if id.ObjectID != "" {
objID = &id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return err
@@ -186,10 +186,10 @@ func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC
if nodeType != html.DocumentNode {
var objID runtime.RemoteObjectID
if id.objectID != "" {
objID = id.objectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return "", err
@@ -238,7 +238,7 @@ func createChildrenArray(nodes []dom.Node) []HTMLElementIdentity {
for idx, child := range nodes {
child := child
children[idx] = HTMLElementIdentity{
nodeID: child.NodeID,
NodeID: child.NodeID,
}
}

View File

@@ -2,15 +2,45 @@ package input
import (
"context"
"github.com/pkg/errors"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/input"
)
type Keyboard struct {
client *cdp.Client
}
const DefaultDelay = 25
type (
KeyboardModifier int
KeyboardLocation int
KeyboardKey struct {
KeyCode int
Key string
Code string
Modifier KeyboardModifier
Location KeyboardLocation
}
Keyboard struct {
client *cdp.Client
}
)
const (
KeyboardModifierNone KeyboardModifier = 0
KeyboardModifierAlt KeyboardModifier = 1
KeyboardModifierCtrl KeyboardModifier = 2
KeyboardModifierCmd KeyboardModifier = 4
KeyboardModifierShift KeyboardModifier = 8
// 1=Left, 2=Right
KeyboardLocationNone KeyboardLocation = 0
KeyboardLocationLeft KeyboardLocation = 1
KeyboardLocationRight KeyboardLocation = 2
)
func NewKeyboard(client *cdp.Client) *Keyboard {
return &Keyboard{client}
@@ -32,7 +62,7 @@ func (k *Keyboard) Up(ctx context.Context, char string) error {
)
}
func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
func (k *Keyboard) Type(ctx context.Context, text string, delay time.Duration) error {
for _, ch := range text {
ch := string(ch)
@@ -40,7 +70,7 @@ func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
return err
}
releaseDelay := randomDuration(delay)
releaseDelay := randomDuration(int(delay))
time.Sleep(releaseDelay)
if err := k.Up(ctx, ch); err != nil {
@@ -50,3 +80,34 @@ func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
return nil
}
func (k *Keyboard) Press(ctx context.Context, name string) error {
key, found := usKeyboardLayout[name]
if !found {
return errors.New("invalid key")
}
err := k.client.Input.DispatchKeyEvent(
ctx,
input.NewDispatchKeyEventArgs("keyDown").
SetCode(key.Code).
SetKey(key.Key).
SetWindowsVirtualKeyCode(key.KeyCode),
)
if err != nil {
return err
}
releaseDelay := randomDuration(DefaultDelay)
time.Sleep(releaseDelay)
return k.client.Input.DispatchKeyEvent(
ctx,
input.NewDispatchKeyEventArgs("keyUp").
SetCode(key.Code).
SetKey(key.Key).
SetWindowsVirtualKeyCode(key.KeyCode),
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,18 +8,27 @@ import (
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
type Manager struct {
client *cdp.Client
exec *eval.ExecutionContext
keyboard *Keyboard
mouse *Mouse
}
type (
TypeParams struct {
Text string
Clear bool
Delay time.Duration
}
Manager struct {
client *cdp.Client
exec *eval.ExecutionContext
keyboard *Keyboard
mouse *Mouse
}
)
func NewManager(
client *cdp.Client,
@@ -61,14 +70,14 @@ func (m *Manager) ScrollIntoView(ctx context.Context, objectID runtime.RemoteObj
)
}
func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector values.String) error {
return m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector.String()))
func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector string) error {
return m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector))
}
func (m *Manager) ScrollByXY(ctx context.Context, x, y values.Float) error {
func (m *Manager) ScrollByXY(ctx context.Context, x, y float64) error {
return m.exec.Eval(
ctx,
templates.Scroll(eval.ParamFloat(float64(x)), eval.ParamFloat(float64(y))),
templates.Scroll(eval.ParamFloat(x), eval.ParamFloat(y)),
)
}
@@ -82,14 +91,14 @@ func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) er
return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID))
}
func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return nil
@@ -98,6 +107,18 @@ func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID,
return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
}
func (m *Manager) Blur(ctx context.Context, objectID runtime.RemoteObjectID) error {
return m.exec.EvalWithArguments(ctx, templates.Blur(), runtime.CallArgument{
ObjectID: &objectID,
})
}
func (m *Manager) BlurBySelector(ctx context.Context, parentObjectID runtime.RemoteObjectID, selector string) error {
return m.exec.EvalWithArguments(ctx, templates.BlurBySelector(selector), runtime.CallArgument{
ObjectID: &parentObjectID,
})
}
func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID) error {
if err := m.ScrollIntoView(ctx, objectID); err != nil {
return err
@@ -112,12 +133,12 @@ func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID
return m.mouse.Move(ctx, q.X, q.Y)
}
func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
@@ -132,15 +153,15 @@ func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.Node
return m.mouse.Move(ctx, q.X, q.Y)
}
func (m *Manager) MoveMouseByXY(ctx context.Context, x, y values.Float) error {
func (m *Manager) MoveMouseByXY(ctx context.Context, x, y float64) error {
if err := m.ScrollByXY(ctx, x, y); err != nil {
return err
}
return m.mouse.Move(ctx, float64(x), float64(y))
return m.mouse.Move(ctx, x, y)
}
func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) error {
func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, count int) error {
if err := m.ScrollIntoView(ctx, objectID); err != nil {
return err
}
@@ -151,19 +172,21 @@ func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) er
return err
}
if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
return nil
}
return nil
}
func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error {
if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
@@ -175,29 +198,30 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID,
return err
}
if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
return nil
}
return nil
}
func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error {
if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil {
return err
}
found, err := m.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector))
if err != nil {
return err
}
for _, nodeID := range found.NodeIDs {
_, min := core.NumberBoundaries(100)
beforeTypeDelay := time.Duration(min)
beforeTypeDelay := time.Duration(core.NumberLowerBoundary(drivers.DefaultMouseDelay*10)) * time.Millisecond
time.Sleep(beforeTypeDelay * time.Millisecond)
time.Sleep(beforeTypeDelay)
points, err := GetClickablePointByNodeID(ctx, m.client, nodeID)
@@ -205,7 +229,9 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI
return err
}
if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
return nil
}
}
@@ -213,7 +239,7 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI
return nil
}
func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, text core.Value, delay values.Int) error {
func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, params TypeParams) error {
err := m.ScrollIntoView(ctx, objectID)
if err != nil {
@@ -226,22 +252,34 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, tex
return err
}
_, min := core.NumberBoundaries(float64(delay))
beforeTypeDelay := time.Duration(min)
if params.Clear {
points, err := GetClickablePointByObjectID(ctx, m.client, objectID)
time.Sleep(beforeTypeDelay * time.Millisecond)
if err != nil {
return err
}
return m.keyboard.Type(ctx, text.String(), int(delay))
if err := m.ClearByXY(ctx, points); err != nil {
return err
}
}
d := core.NumberLowerBoundary(float64(params.Delay))
beforeTypeDelay := time.Duration(d)
time.Sleep(beforeTypeDelay)
return m.keyboard.Type(ctx, params.Text, params.Delay)
}
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, text core.Value, delay values.Int) error {
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, params TypeParams) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
@@ -253,12 +291,85 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s
return err
}
_, min := core.NumberBoundaries(float64(delay))
beforeTypeDelay := time.Duration(min)
if params.Clear {
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
time.Sleep(beforeTypeDelay * time.Millisecond)
if err != nil {
return err
}
return m.keyboard.Type(ctx, text.String(), int(delay))
if err := m.ClearByXY(ctx, points); err != nil {
return err
}
}
d := core.NumberLowerBoundary(float64(params.Delay))
beforeTypeDelay := time.Duration(d)
time.Sleep(beforeTypeDelay)
return m.keyboard.Type(ctx, params.Text, params.Delay)
}
func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) error {
err := m.ScrollIntoView(ctx, objectID)
if err != nil {
return err
}
points, err := GetClickablePointByObjectID(ctx, m.client, objectID)
if err != nil {
return err
}
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID))
if err != nil {
return err
}
return m.ClearByXY(ctx, points)
}
func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
}
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
if err != nil {
return err
}
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
if err != nil {
return err
}
return m.ClearByXY(ctx, points)
}
func (m *Manager) ClearByXY(ctx context.Context, points Quad) error {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, 2)
if err != nil {
return err
}
return m.keyboard.Press(ctx, "Backspace")
}
func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, value *values.Array) (*values.Array, error) {
@@ -283,12 +394,12 @@ func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, v
return arr, nil
}
func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, value *values.Array) (*values.Array, error) {
func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, value *values.Array) (*values.Array, error) {
if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil {
return values.NewArray(0), err
}
res, err := m.exec.EvalWithReturnValue(ctx, templates.SelectBySelector(selector.String(), value.String()))
res, err := m.exec.EvalWithReturnValue(ctx, templates.SelectBySelector(selector, value.String()))
if err != nil {
return values.NewArray(0), err

View File

@@ -18,37 +18,47 @@ func NewMouse(client *cdp.Client) *Mouse {
return &Mouse{client, 0, 0}
}
func (m *Mouse) Click(ctx context.Context, x, y float64, delay int) error {
func (m *Mouse) Click(ctx context.Context, x, y float64, delay time.Duration) error {
return m.ClickWithCount(ctx, x, y, delay, 1)
}
func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, delay time.Duration, count int) error {
if err := m.Move(ctx, x, y); err != nil {
return err
}
if err := m.Down(ctx, "left"); err != nil {
if err := m.DownWithCount(ctx, "left", count); err != nil {
return err
}
releaseDelay := randomDuration(delay)
time.Sleep(randomDuration(int(delay)))
time.Sleep(releaseDelay * time.Millisecond)
return m.Up(ctx, "left")
return m.UpWithCount(ctx, "left", count)
}
func (m *Mouse) Down(ctx context.Context, button string) error {
return m.DownWithCount(ctx, button, 1)
}
func (m *Mouse) DownWithCount(ctx context.Context, button string, count int) error {
return m.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mousePressed", m.x, m.y).
SetClickCount(1).
SetButton(button),
SetButton(button).
SetClickCount(count),
)
}
func (m *Mouse) Up(ctx context.Context, button string) error {
return m.UpWithCount(ctx, button, 1)
}
func (m *Mouse) UpWithCount(ctx context.Context, button string, count int) error {
return m.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mouseReleased", m.x, m.y).
SetClickCount(1).
SetButton(button),
SetButton(button).
SetClickCount(count),
)
}

View File

@@ -1,5 +1,7 @@
package cdp
import "github.com/MontFerret/ferret/pkg/drivers"
type (
Options struct {
Name string
@@ -7,6 +9,8 @@ type (
UserAgent string
Address string
KeepCookies bool
Headers drivers.HTTPHeaders
Cookies drivers.HTTPCookies
}
Option func(opts *Options)
@@ -57,3 +61,47 @@ func WithCustomName(name string) Option {
opts.Name = name
}
}
func WithHeader(name string, value []string) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
opts.Headers[name] = value
}
}
func WithHeaders(headers drivers.HTTPHeaders) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
for k, v := range headers {
opts.Headers[k] = v
}
}
}
func WithCookie(cookie drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
opts.Cookies[cookie.Name] = cookie
}
}
func WithCookies(cookies []drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
for _, c := range cookies {
opts.Cookies[c.Name] = c
}
}
}

View File

@@ -144,7 +144,7 @@ func LoadHTMLPage(
return nil, err
}
if params.Cookies != nil {
if len(params.Cookies) > 0 {
cookies := make([]network.CookieParam, 0, len(params.Cookies))
for _, c := range params.Cookies {
@@ -167,7 +167,7 @@ func LoadHTMLPage(
}
}
if params.Headers != nil {
if len(params.Headers) > 0 {
j, err := json.Marshal(params.Headers)
if err != nil {

View File

@@ -0,0 +1,28 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
)
func Blur() string {
return `
(el) => {
el.blur()
}
`
}
func BlurBySelector(selector string) string {
return fmt.Sprintf(`
(parent) => {
const el = parent.querySelector('%s');
if (el == null) {
throw new Error('%s')
}
el.blur();
}
`, selector, drivers.ErrNotFound)
}

View File

@@ -121,7 +121,11 @@ func GetInDocument(ctx context.Context, doc drivers.HTMLDocument, path []core.Va
return GetInDocument(ctx, parent, path[1:])
case "body", "head":
out := doc.QuerySelector(ctx, segment)
out, err := doc.QuerySelector(ctx, segment)
if err != nil {
return values.None, err
}
if out == values.None {
return out, nil
@@ -166,9 +170,13 @@ func GetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
case "innerHTML":
return el.GetInnerHTML(ctx)
case "value":
return el.GetValue(ctx), nil
return el.GetValue(ctx)
case "attributes":
attrs := el.GetAttributes(ctx)
attrs, err := el.GetAttributes(ctx)
if err != nil {
return values.None, err
}
if len(path) == 1 {
return attrs, nil
@@ -209,7 +217,7 @@ func GetInNode(ctx context.Context, node drivers.HTMLNode, path []core.Value) (c
if nt == drivers.HTMLElementType || nt == drivers.HTMLDocumentType {
re := node.(drivers.HTMLNode)
return re.GetChildNode(ctx, segment.(values.Int)), nil
return re.GetChildNode(ctx, values.ToInt(segment))
}
return values.GetIn(ctx, node, path[1:])
@@ -224,7 +232,11 @@ func GetInNode(ctx context.Context, node drivers.HTMLNode, path []core.Value) (c
case "nodeName":
return node.GetNodeName(), nil
case "children":
children := node.GetChildNodes(ctx)
children, err := node.GetChildNodes(ctx)
if err != nil {
return values.None, err
}
if len(path) == 1 {
return children, nil

View File

@@ -26,7 +26,11 @@ func NewIterator(
func (iterator *Iterator) Next(ctx context.Context) (value core.Value, key core.Value, err error) {
if iterator.node.Length() > iterator.pos {
idx := iterator.pos
val := iterator.node.GetChildNode(ctx, idx)
val, err := iterator.node.GetChildNode(ctx, idx)
if err != nil {
return values.None, values.None, err
}
iterator.pos++

View File

@@ -49,7 +49,11 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
return err
}
curr := el.GetAttributes(ctx)
curr, err := el.GetAttributes(ctx)
if err != nil {
return err
}
// remove all previous attributes
err = el.RemoveAttribute(ctx, curr.Keys()...)

9
pkg/drivers/consts.go Normal file
View File

@@ -0,0 +1,9 @@
package drivers
const (
DefaultPageLoadTimeout = 60000
DefaultWaitTimeout = 5000
DefaultKeyboardDelay = 25
DefaultMouseDelay = 10
DefaultTimeout = 30000
)

View File

@@ -30,6 +30,8 @@ type (
HTTPOnly bool
SameSite SameSite
}
HTTPCookies map[string]HTTPCookie
)
const (

View File

@@ -2,14 +2,10 @@ package drivers
import (
"context"
"io"
"time"
"github.com/MontFerret/ferret/pkg/runtime/core"
"io"
)
const DefaultTimeout = time.Second * 30
type (
ctxKey struct{}

View File

@@ -1,15 +1,9 @@
package drivers
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
func WithDefaultTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, DefaultTimeout)
}
func ToPage(value core.Value) (HTMLPage, error) {
err := core.ValidateType(value, HTMLPageType)

View File

@@ -115,7 +115,7 @@ func (doc *HTMLDocument) Copy() core.Value {
return cp
}
func (doc *HTMLDocument) Clone() core.Value {
func (doc *HTMLDocument) Clone() core.Cloneable {
cloned, err := NewHTMLDocument(doc.doc, doc.url.String(), doc.parent)
if err != nil {
@@ -149,23 +149,23 @@ func (doc *HTMLDocument) GetNodeName() values.String {
return "#document"
}
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) core.Value {
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) (*values.Array, error) {
return doc.element.GetChildNodes(ctx)
}
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) core.Value {
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
return doc.element.GetChildNode(ctx, idx)
}
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
return doc.element.QuerySelector(ctx, selector)
}
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
return doc.element.QuerySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) values.Int {
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
return doc.element.CountBySelector(ctx, selector)
}
@@ -207,30 +207,6 @@ func (doc *HTMLDocument) GetParentDocument() drivers.HTMLDocument {
return doc.parent
}
func (doc *HTMLDocument) ClickBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) ClickBySelectorAll(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) InputBySelector(_ context.Context, _ values.String, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) SelectBySelector(_ context.Context, _ values.String, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) PrintToPDF(_ context.Context, _ drivers.PDFParams) (values.Binary, error) {
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) CaptureScreenshot(_ context.Context, _ drivers.ScreenshotParams) (values.Binary, error) {
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) ScrollTop(_ context.Context) error {
return core.ErrNotSupported
}
@@ -247,22 +223,10 @@ func (doc *HTMLDocument) ScrollByXY(_ context.Context, _, _ values.Float) error
return core.ErrNotSupported
}
func (doc *HTMLDocument) FocusBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) MoveMouseBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) MoveMouseByXY(_ context.Context, _, _ values.Float) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForNavigation(_ context.Context) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForElement(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}

View File

@@ -26,7 +26,7 @@ func NewDriver(opts ...Option) *Driver {
drv := new(Driver)
drv.options = newOptions(opts)
if drv.options.proxy == "" {
if drv.options.Proxy == "" {
drv.client = pester.New()
} else {
client, err := newClientWithProxy(drv.options)
@@ -38,15 +38,15 @@ func NewDriver(opts ...Option) *Driver {
}
}
drv.client.Concurrency = drv.options.concurrency
drv.client.MaxRetries = drv.options.maxRetries
drv.client.Backoff = drv.options.backoff
drv.client.Concurrency = drv.options.Concurrency
drv.client.MaxRetries = drv.options.MaxRetries
drv.client.Backoff = drv.options.Backoff
return drv
}
func newClientWithProxy(options *Options) (*http.Client, error) {
proxyURL, err := url.Parse(options.proxy)
proxyURL, err := url.Parse(options.Proxy)
if err != nil {
return nil, err
@@ -59,7 +59,7 @@ func newClientWithProxy(options *Options) (*http.Client, error) {
}
func (drv *Driver) Name() string {
return DriverName
return drv.options.Name
}
func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTMLPage, error) {
@@ -76,33 +76,54 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
if params.Headers != nil {
for k := range params.Headers {
req.Header.Add(k, params.Headers.Get(k))
if drv.options.Headers != nil && params.Headers == nil {
params.Headers = make(drivers.HTTPHeaders)
}
logger.
Debug().
Timestamp().
Str("header", k).
Msg("set header")
// Set default headers
for k, v := range drv.options.Headers {
_, exists := params.Headers[k]
// do not override user's set values
if !exists {
params.Headers[k] = v
}
}
if params.Cookies != nil {
for _, c := range params.Cookies {
req.AddCookie(&http.Cookie{
Name: c.Name,
Value: c.Value,
})
for k := range params.Headers {
req.Header.Add(k, params.Headers.Get(k))
logger.
Debug().
Timestamp().
Str("cookie", c.Name).
Msg("set cookie")
logger.
Debug().
Timestamp().
Str("header", k).
Msg("set header")
}
if drv.options.Cookies != nil && params.Cookies == nil {
params.Cookies = make(drivers.HTTPCookies)
}
// set default cookies
for k, v := range drv.options.Cookies {
_, exists := params.Cookies[k]
// do not override user's set values
if !exists {
params.Cookies[k] = v
}
}
for _, c := range params.Cookies {
req.AddCookie(fromDriverCookie(c))
logger.
Debug().
Timestamp().
Str("cookie", c.Name).
Msg("set cookie")
}
req = req.WithContext(ctx)
var ua string
@@ -110,7 +131,7 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
if params.UserAgent != "" {
ua = common.GetUserAgent(params.UserAgent)
} else {
ua = common.GetUserAgent(drv.options.userAgent)
ua = common.GetUserAgent(drv.options.UserAgent)
}
logger.
@@ -141,6 +162,12 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
return nil, errors.Wrapf(err, "failed to parse a document %s", params.URL)
}
cookies, err := toDriverCookies(resp.Cookies())
if err != nil {
return nil, err
}
// HTTPResponse is temporarily unavailable when the status code != OK
r := drivers.HTTPResponse{
StatusCode: resp.StatusCode,
@@ -148,7 +175,7 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
Headers: drivers.HTTPHeaders(resp.Header),
}
return NewHTMLPage(doc, params.URL, params.Cookies, &r)
return NewHTMLPage(doc, params.URL, &r, cookies)
}
func (drv *Driver) Parse(_ context.Context, str values.String) (drivers.HTMLPage, error) {

View File

@@ -116,14 +116,14 @@ func (el *HTMLElement) Length() values.Int {
return el.children.Length()
}
func (el *HTMLElement) GetValue(_ context.Context) core.Value {
func (el *HTMLElement) GetValue(_ context.Context) (core.Value, error) {
val, ok := el.selection.Attr("value")
if ok {
return values.NewString(val)
return values.NewString(val), nil
}
return values.EmptyString
return values.EmptyString, nil
}
func (el *HTMLElement) SetValue(_ context.Context, value core.Value) error {
@@ -242,16 +242,16 @@ func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object)
return err
}
func (el *HTMLElement) GetAttributes(_ context.Context) *values.Object {
func (el *HTMLElement) GetAttributes(_ context.Context) (*values.Object, error) {
el.ensureAttrs()
return el.attrs.Copy().(*values.Object)
return el.attrs.Copy().(*values.Object), nil
}
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) (core.Value, error) {
el.ensureAttrs()
return el.attrs.MustGet(name)
return el.attrs.MustGet(name), nil
}
func (el *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
@@ -274,43 +274,43 @@ func (el *HTMLElement) RemoveAttribute(_ context.Context, name ...values.String)
return nil
}
func (el *HTMLElement) GetChildNodes(_ context.Context) core.Value {
func (el *HTMLElement) GetChildNodes(_ context.Context) (*values.Array, error) {
if el.children == nil {
el.children = el.parseChildren()
}
return el.children
return el.children.Copy().(*values.Array), nil
}
func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) (core.Value, error) {
if el.children == nil {
el.children = el.parseChildren()
}
return el.children.Get(idx)
return el.children.Get(idx), nil
}
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) (core.Value, error) {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.None
return values.None, nil
}
res, err := NewHTMLElement(selection)
if err != nil {
return values.None
return values.None, err
}
return res
return res, nil
}
func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) (*values.Array, error) {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.None
return values.NewArray(0), nil
}
arr := values.NewArray(selection.Length())
@@ -323,7 +323,7 @@ func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.Strin
}
})
return arr
return arr, nil
}
func (el *HTMLElement) XPath(_ context.Context, expression values.String) (core.Value, error) {
@@ -457,14 +457,14 @@ func (el *HTMLElement) GetInnerTextBySelectorAll(_ context.Context, selector val
return arr, nil
}
func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) (values.Int, error) {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.ZeroInt
return values.ZeroInt, nil
}
return values.NewInt(selection.Size())
return values.NewInt(selection.Size()), nil
}
func (el *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) (values.Boolean, error) {
@@ -489,15 +489,23 @@ func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
return common.NewIterator(el)
}
func (el *HTMLElement) Click(_ context.Context) error {
func (el *HTMLElement) Click(_ context.Context, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClickBySelector(_ context.Context, _ values.String) error {
func (el *HTMLElement) ClickBySelector(_ context.Context, _ values.String, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String) error {
func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Clear(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClearBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
@@ -505,10 +513,18 @@ func (el *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) erro
return core.ErrNotSupported
}
func (el *HTMLElement) InputBySelector(_ context.Context, _ values.String, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (el *HTMLElement) SelectBySelector(_ context.Context, _ values.String, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (el *HTMLElement) ScrollIntoView(_ context.Context) error {
return core.ErrNotSupported
}
@@ -517,10 +533,26 @@ func (el *HTMLElement) Focus(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) FocusBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Blur(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) BlurBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Hover(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) HoverBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
@@ -548,7 +580,11 @@ func (el *HTMLElement) ensureStyles(ctx context.Context) error {
}
func (el *HTMLElement) parseStyles(ctx context.Context) (*values.Object, error) {
str := el.GetAttribute(ctx, "style")
str, err := el.GetAttribute(ctx, "style")
if err != nil {
return values.NewObject(), err
}
if str == values.None {
return values.NewObject(), nil

View File

@@ -322,8 +322,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
v := el.GetValue(context.Background())
v, err := el.GetValue(context.Background())
So(err, ShouldBeNil)
So(v, ShouldEqual, "find")
})
@@ -393,8 +394,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
found := el.QuerySelector(context.Background(), values.NewString("body .card-img-top:nth-child(1)"))
found, err := el.QuerySelector(context.Background(), values.NewString("body .card-img-top:nth-child(1)"))
So(err, ShouldBeNil)
So(found, ShouldNotEqual, values.None)
v := found.(drivers.HTMLNode).GetNodeName()
@@ -415,8 +417,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
v := el.CountBySelector(context.Background(), values.NewString("head meta"))
v, err := el.CountBySelector(context.Background(), values.NewString("head meta"))
So(err, ShouldBeNil)
So(v, ShouldEqual, 4)
})
}

View File

@@ -2,14 +2,16 @@ package http
import (
"bytes"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
HTTP "net/http"
"github.com/PuerkitoBio/goquery"
"github.com/antchfx/htmlquery"
"github.com/antchfx/xpath"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func parseXPathNode(nav *htmlquery.NodeNavigator) (core.Value, error) {
@@ -45,3 +47,71 @@ func outerHTML(s *goquery.Selection) (string, error) {
return buf.String(), nil
}
func toDriverCookies(cookies []*HTTP.Cookie) (drivers.HTTPCookies, error) {
res := make(drivers.HTTPCookies)
for _, c := range cookies {
dc, err := toDriverCookie(c)
if err != nil {
return nil, err
}
res[dc.Name] = dc
}
return res, nil
}
func toDriverCookie(cookie *HTTP.Cookie) (drivers.HTTPCookie, error) {
res := drivers.HTTPCookie{}
if cookie == nil {
return res, core.Error(core.ErrMissedArgument, "cookie")
}
res.Name = cookie.Name
res.Value = cookie.Value
res.Path = cookie.Path
res.Domain = cookie.Domain
res.Expires = cookie.Expires
res.MaxAge = cookie.MaxAge
res.Secure = cookie.Secure
res.HTTPOnly = cookie.HttpOnly
switch cookie.SameSite {
case HTTP.SameSiteLaxMode:
res.SameSite = drivers.SameSiteLaxMode
case HTTP.SameSiteStrictMode:
res.SameSite = drivers.SameSiteStrictMode
default:
res.SameSite = drivers.SameSiteDefaultMode
}
return res, nil
}
func fromDriverCookie(cookie drivers.HTTPCookie) *HTTP.Cookie {
res := &HTTP.Cookie{}
res.Name = cookie.Name
res.Value = cookie.Value
res.Path = cookie.Path
res.Domain = cookie.Domain
res.Expires = cookie.Expires
res.MaxAge = cookie.MaxAge
res.Secure = cookie.Secure
res.HttpOnly = cookie.HTTPOnly
switch cookie.SameSite {
case drivers.SameSiteLaxMode:
res.SameSite = HTTP.SameSiteLaxMode
case drivers.SameSiteStrictMode:
res.SameSite = HTTP.SameSiteStrictMode
default:
res.SameSite = HTTP.SameSiteDefaultMode
}
return res
}

View File

@@ -1,6 +1,7 @@
package http
import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/sethgrid/pester"
)
@@ -8,19 +9,23 @@ type (
Option func(opts *Options)
Options struct {
backoff pester.BackoffStrategy
maxRetries int
concurrency int
proxy string
userAgent string
Name string
Backoff pester.BackoffStrategy
MaxRetries int
Concurrency int
Proxy string
UserAgent string
Headers drivers.HTTPHeaders
Cookies drivers.HTTPCookies
}
)
func newOptions(setters []Option) *Options {
opts := new(Options)
opts.backoff = pester.ExponentialBackoff
opts.concurrency = 3
opts.maxRetries = 5
opts.Name = DriverName
opts.Backoff = pester.ExponentialBackoff
opts.Concurrency = 3
opts.MaxRetries = 5
for _, setter := range setters {
setter(opts)
@@ -31,42 +36,92 @@ func newOptions(setters []Option) *Options {
func WithDefaultBackoff() Option {
return func(opts *Options) {
opts.backoff = pester.DefaultBackoff
opts.Backoff = pester.DefaultBackoff
}
}
func WithLinearBackoff() Option {
return func(opts *Options) {
opts.backoff = pester.LinearBackoff
opts.Backoff = pester.LinearBackoff
}
}
func WithExponentialBackoff() Option {
return func(opts *Options) {
opts.backoff = pester.ExponentialBackoff
opts.Backoff = pester.ExponentialBackoff
}
}
func WithMaxRetries(value int) Option {
return func(opts *Options) {
opts.maxRetries = value
opts.MaxRetries = value
}
}
func WithConcurrency(value int) Option {
return func(opts *Options) {
opts.concurrency = value
opts.Concurrency = value
}
}
func WithProxy(address string) Option {
return func(opts *Options) {
opts.proxy = address
opts.Proxy = address
}
}
func WithUserAgent(value string) Option {
return func(opts *Options) {
opts.userAgent = value
opts.UserAgent = value
}
}
func WithCustomName(name string) Option {
return func(opts *Options) {
opts.Name = name
}
}
func WithHeader(name string, value []string) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
opts.Headers[name] = value
}
}
func WithHeaders(headers drivers.HTTPHeaders) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
for k, v := range headers {
opts.Headers[k] = v
}
}
}
func WithCookie(cookie drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
opts.Cookies[cookie.Name] = cookie
}
}
func WithCookies(cookies []drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
for _, c := range cookies {
opts.Cookies[c.Name] = c
}
}
}

View File

@@ -4,16 +4,17 @@ import (
"context"
"hash/fnv"
"github.com/PuerkitoBio/goquery"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/PuerkitoBio/goquery"
)
type HTMLPage struct {
document *HTMLDocument
cookies []drivers.HTTPCookie
cookies drivers.HTTPCookies
frames *values.Array
response *drivers.HTTPResponse
}
@@ -21,8 +22,8 @@ type HTMLPage struct {
func NewHTMLPage(
qdoc *goquery.Document,
url string,
cookies []drivers.HTTPCookie,
response *drivers.HTTPResponse,
cookies drivers.HTTPCookies,
) (*HTMLPage, error) {
doc, err := NewRootHTMLDocument(qdoc, url)
@@ -83,11 +84,17 @@ func (p *HTMLPage) Hash() uint64 {
}
func (p *HTMLPage) Copy() core.Value {
cookies := make(drivers.HTTPCookies)
for k, v := range p.cookies {
cookies[k] = v
}
page, err := NewHTMLPage(
p.document.doc,
p.document.GetURL().String(),
p.cookies,
p.response,
cookies,
)
if err != nil {

View File

@@ -13,7 +13,7 @@ type (
URL string
UserAgent string
KeepCookies bool
Cookies []HTTPCookie
Cookies HTTPCookies
Headers HTTPHeaders
Viewport *Viewport
}

View File

@@ -30,23 +30,19 @@ type (
GetNodeName() values.String
GetChildNodes(ctx context.Context) core.Value
GetChildNodes(ctx context.Context) (*values.Array, error)
GetChildNode(ctx context.Context, idx values.Int) core.Value
GetChildNode(ctx context.Context, idx values.Int) (core.Value, error)
QuerySelector(ctx context.Context, selector values.String) core.Value
QuerySelector(ctx context.Context, selector values.String) (core.Value, error)
QuerySelectorAll(ctx context.Context, selector values.String) core.Value
QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error)
CountBySelector(ctx context.Context, selector values.String) values.Int
CountBySelector(ctx context.Context, selector values.String) (values.Int, error)
ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error)
XPath(ctx context.Context, expression values.String) (core.Value, error)
ClickBySelector(ctx context.Context, selector values.String) error
ClickBySelectorAll(ctx context.Context, selector values.String) error
}
// HTMLElement is the most general base interface which most objects in a GetMainFrame implement.
@@ -61,7 +57,7 @@ type (
SetInnerHTML(ctx context.Context, innerHTML values.String) error
GetValue(ctx context.Context) core.Value
GetValue(ctx context.Context) (core.Value, error)
SetValue(ctx context.Context, value core.Value) error
@@ -75,9 +71,9 @@ type (
RemoveStyle(ctx context.Context, name ...values.String) error
GetAttributes(ctx context.Context) *values.Object
GetAttributes(ctx context.Context) (*values.Object, error)
GetAttribute(ctx context.Context, name values.String) core.Value
GetAttribute(ctx context.Context, name values.String) (core.Value, error)
SetAttributes(ctx context.Context, values *values.Object) error
@@ -97,18 +93,38 @@ type (
GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error)
Click(ctx context.Context) error
Click(ctx context.Context, count values.Int) error
ClickBySelector(ctx context.Context, selector values.String, count values.Int) error
ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error
Clear(ctx context.Context) error
ClearBySelector(ctx context.Context, selector values.String) error
Input(ctx context.Context, value core.Value, delay values.Int) error
InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error
Select(ctx context.Context, value *values.Array) (*values.Array, error)
SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error)
ScrollIntoView(ctx context.Context) error
Focus(ctx context.Context) error
FocusBySelector(ctx context.Context, selector values.String) error
Blur(ctx context.Context) error
BlurBySelector(ctx context.Context, selector values.String) error
Hover(ctx context.Context) error
HoverBySelector(ctx context.Context, selector values.String) error
WaitForAttribute(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
WaitForStyle(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
@@ -131,10 +147,6 @@ type (
GetChildDocuments(ctx context.Context) (*values.Array, error)
InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error
SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error)
ScrollTop(ctx context.Context) error
ScrollBottom(ctx context.Context) error
@@ -143,12 +155,8 @@ type (
ScrollByXY(ctx context.Context, x, y values.Float) error
FocusBySelector(ctx context.Context, selector values.String) error
MoveMouseByXY(ctx context.Context, x, y values.Float) error
MoveMouseBySelector(ctx context.Context, selector values.String) error
WaitForElement(ctx context.Context, selector values.String, when WaitEvent) error
WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error

View File

@@ -78,8 +78,7 @@ In: 'IN';
// Literals
Param: '@';
Identifier: Letter+ (Symbols (Identifier)*)* (Digit (Identifier)*)*;
StringLiteral: SQString | DQSring;
TemplateStringLiteral: '`' ('\\`' | ~'`')* '`';
StringLiteral: SQString | DQSring | BacktickString | TickString;
IntegerLiteral: [0-9]+;
FloatLiteral
: DecimalIntegerLiteral Dot [0-9]+ ExponentPart?
@@ -108,4 +107,6 @@ fragment Digit
;
fragment DQSring: '"' ( '\\'. | '""' | ~('"'| '\\') )* '"';
fragment SQString: '\'' ('\\'. | '\'\'' | ~('\'' | '\\'))* '\'';
fragment BacktickString: '`' ('\\`' | ~'`')* '`';
fragment TickString: '´' ('\\´' | ~'´')* '´';
fragment NamespaceSeparator: '::';

View File

@@ -57,10 +57,9 @@ In=56
Param=57
Identifier=58
StringLiteral=59
TemplateStringLiteral=60
IntegerLiteral=61
FloatLiteral=62
NamespaceSegment=63
IntegerLiteral=60
FloatLiteral=61
NamespaceSegment=62
':'=5
';'=6
'.'=7

View File

@@ -159,7 +159,6 @@ booleanLiteral
stringLiteral
: StringLiteral
| TemplateStringLiteral
;
integerLiteral
@@ -185,11 +184,6 @@ propertyAssignment
| shorthandPropertyName
;
memberExpression
: Identifier (Dot propertyName (computedPropertyName)*)+
| Identifier computedPropertyName (Dot propertyName (computedPropertyName)*)* (computedPropertyName (Dot propertyName)*)*
;
shorthandPropertyName
: variable
;
@@ -201,6 +195,7 @@ computedPropertyName
propertyName
: Identifier
| stringLiteral
| param
;
expressionGroup
@@ -215,6 +210,21 @@ functionCallExpression
: namespace Identifier arguments
;
member
: Identifier
| functionCallExpression
| param
;
memberPath
: (Dot propertyName (computedPropertyName)*)+
| computedPropertyName (Dot propertyName (computedPropertyName)*)* (computedPropertyName (Dot propertyName)*)*
;
memberExpression
: member memberPath
;
arguments
: OpenParen(expression (Comma expression)*)?CloseParen
;

File diff suppressed because one or more lines are too long

View File

@@ -57,10 +57,9 @@ In=56
Param=57
Identifier=58
StringLiteral=59
TemplateStringLiteral=60
IntegerLiteral=61
FloatLiteral=62
NamespaceSegment=63
IntegerLiteral=60
FloatLiteral=61
NamespaceSegment=62
':'=5
';'=6
'.'=7

File diff suppressed because one or more lines are too long

View File

@@ -57,10 +57,9 @@ In=56
Param=57
Identifier=58
StringLiteral=59
TemplateStringLiteral=60
IntegerLiteral=61
FloatLiteral=62
NamespaceSegment=63
IntegerLiteral=60
FloatLiteral=61
NamespaceSegment=62
':'=5
';'=6
'.'=7

View File

@@ -14,7 +14,7 @@ var _ = fmt.Printf
var _ = unicode.IsLetter
var serializedLexerAtn = []uint16{
3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 65, 529,
3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 64, 544,
8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7,
9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12,
4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4,
@@ -28,59 +28,61 @@ var serializedLexerAtn = []uint16{
4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4,
60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65,
9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 4, 70, 9,
70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 3, 2, 3, 2, 3, 2, 3, 2, 7,
2, 152, 10, 2, 12, 2, 14, 2, 155, 11, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2,
3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 166, 10, 3, 12, 3, 14, 3, 169, 11, 3, 3,
3, 3, 3, 3, 4, 6, 4, 174, 10, 4, 13, 4, 14, 4, 175, 3, 4, 3, 4, 3, 5, 3,
5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3,
10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15,
3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3,
20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24,
3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3,
28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 5, 29, 241, 10, 29, 3, 30, 3, 30,
3, 30, 3, 30, 5, 30, 247, 10, 30, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3,
33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36,
3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3,
38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 39, 3, 39,
3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 41, 3, 41, 3,
41, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 42, 3, 43, 3, 43, 3, 43,
3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3,
44, 3, 44, 5, 44, 319, 10, 44, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 46,
3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3,
47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47,
3, 47, 5, 47, 349, 10, 47, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 49, 3,
49, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 50, 3, 50, 3, 51, 3, 51,
3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 52, 3, 53, 3, 53, 3,
53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54,
3, 54, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 3, 56, 5,
56, 399, 10, 56, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 6, 59, 407,
10, 59, 13, 59, 14, 59, 408, 3, 59, 3, 59, 7, 59, 413, 10, 59, 12, 59,
14, 59, 416, 11, 59, 7, 59, 418, 10, 59, 12, 59, 14, 59, 421, 11, 59, 3,
59, 3, 59, 7, 59, 425, 10, 59, 12, 59, 14, 59, 428, 11, 59, 7, 59, 430,
10, 59, 12, 59, 14, 59, 433, 11, 59, 3, 60, 3, 60, 5, 60, 437, 10, 60,
3, 61, 3, 61, 3, 61, 3, 61, 7, 61, 443, 10, 61, 12, 61, 14, 61, 446, 11,
61, 3, 61, 3, 61, 3, 62, 6, 62, 451, 10, 62, 13, 62, 14, 62, 452, 3, 63,
3, 63, 3, 63, 6, 63, 458, 10, 63, 13, 63, 14, 63, 459, 3, 63, 5, 63, 463,
10, 63, 3, 63, 3, 63, 5, 63, 467, 10, 63, 5, 63, 469, 10, 63, 3, 64, 3,
64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 7, 66, 479, 10, 66, 12, 66,
14, 66, 482, 11, 66, 5, 66, 484, 10, 66, 3, 67, 3, 67, 5, 67, 488, 10,
67, 3, 67, 6, 67, 491, 10, 67, 13, 67, 14, 67, 492, 3, 68, 3, 68, 3, 69,
3, 69, 3, 70, 3, 70, 3, 71, 3, 71, 3, 71, 3, 71, 3, 71, 3, 71, 7, 71, 507,
10, 71, 12, 71, 14, 71, 510, 11, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72,
3, 72, 3, 72, 3, 72, 7, 72, 520, 10, 72, 12, 72, 14, 72, 523, 11, 72, 3,
72, 3, 72, 3, 73, 3, 73, 3, 73, 3, 153, 2, 74, 3, 3, 5, 4, 7, 5, 9, 6,
11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 13, 25, 14, 27, 15, 29,
16, 31, 17, 33, 18, 35, 19, 37, 20, 39, 21, 41, 22, 43, 23, 45, 24, 47,
25, 49, 26, 51, 27, 53, 28, 55, 29, 57, 30, 59, 31, 61, 32, 63, 33, 65,
34, 67, 35, 69, 36, 71, 37, 73, 38, 75, 39, 77, 40, 79, 41, 81, 42, 83,
43, 85, 44, 87, 45, 89, 46, 91, 47, 93, 48, 95, 49, 97, 50, 99, 51, 101,
52, 103, 53, 105, 54, 107, 55, 109, 56, 111, 57, 113, 58, 115, 59, 117,
60, 119, 61, 121, 62, 123, 63, 125, 64, 127, 65, 129, 2, 131, 2, 133, 2,
135, 2, 137, 2, 139, 2, 141, 2, 143, 2, 145, 2, 3, 2, 13, 5, 2, 12, 12,
15, 15, 8234, 8235, 6, 2, 11, 11, 13, 14, 34, 34, 162, 162, 3, 2, 98, 98,
3, 2, 50, 59, 5, 2, 50, 59, 67, 72, 99, 104, 3, 2, 51, 59, 4, 2, 71, 71,
103, 103, 4, 2, 45, 45, 47, 47, 4, 2, 67, 92, 99, 124, 4, 2, 36, 36, 94,
94, 4, 2, 41, 41, 94, 94, 2, 552, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2,
70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 4, 74, 9, 74, 3, 2, 3, 2,
3, 2, 3, 2, 7, 2, 154, 10, 2, 12, 2, 14, 2, 157, 11, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 168, 10, 3, 12, 3, 14, 3,
171, 11, 3, 3, 3, 3, 3, 3, 4, 6, 4, 176, 10, 4, 13, 4, 14, 4, 177, 3, 4,
3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9,
3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3,
14, 3, 15, 3, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 19,
3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3,
23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27,
3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 5, 29, 243, 10,
29, 3, 30, 3, 30, 3, 30, 3, 30, 5, 30, 249, 10, 30, 3, 31, 3, 31, 3, 31,
3, 32, 3, 32, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3,
36, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37,
3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3,
39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40,
3, 41, 3, 41, 3, 41, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 42, 3,
43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44,
3, 44, 3, 44, 3, 44, 3, 44, 5, 44, 321, 10, 44, 3, 45, 3, 45, 3, 45, 3,
45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47,
3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3,
47, 3, 47, 3, 47, 3, 47, 5, 47, 351, 10, 47, 3, 48, 3, 48, 3, 48, 3, 48,
3, 48, 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 50, 3,
50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 52,
3, 53, 3, 53, 3, 53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3,
54, 3, 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56,
3, 56, 3, 56, 5, 56, 401, 10, 56, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 3,
59, 6, 59, 409, 10, 59, 13, 59, 14, 59, 410, 3, 59, 3, 59, 7, 59, 415,
10, 59, 12, 59, 14, 59, 418, 11, 59, 7, 59, 420, 10, 59, 12, 59, 14, 59,
423, 11, 59, 3, 59, 3, 59, 7, 59, 427, 10, 59, 12, 59, 14, 59, 430, 11,
59, 7, 59, 432, 10, 59, 12, 59, 14, 59, 435, 11, 59, 3, 60, 3, 60, 3, 60,
3, 60, 5, 60, 441, 10, 60, 3, 61, 6, 61, 444, 10, 61, 13, 61, 14, 61, 445,
3, 62, 3, 62, 3, 62, 6, 62, 451, 10, 62, 13, 62, 14, 62, 452, 3, 62, 5,
62, 456, 10, 62, 3, 62, 3, 62, 5, 62, 460, 10, 62, 5, 62, 462, 10, 62,
3, 63, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 7, 65, 472, 10,
65, 12, 65, 14, 65, 475, 11, 65, 5, 65, 477, 10, 65, 3, 66, 3, 66, 5, 66,
481, 10, 66, 3, 66, 6, 66, 484, 10, 66, 13, 66, 14, 66, 485, 3, 67, 3,
67, 3, 68, 3, 68, 3, 69, 3, 69, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70,
7, 70, 500, 10, 70, 12, 70, 14, 70, 503, 11, 70, 3, 70, 3, 70, 3, 71, 3,
71, 3, 71, 3, 71, 3, 71, 3, 71, 7, 71, 513, 10, 71, 12, 71, 14, 71, 516,
11, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72, 3, 72, 7, 72, 524, 10, 72, 12,
72, 14, 72, 527, 11, 72, 3, 72, 3, 72, 3, 73, 3, 73, 3, 73, 3, 73, 7, 73,
535, 10, 73, 12, 73, 14, 73, 538, 11, 73, 3, 73, 3, 73, 3, 74, 3, 74, 3,
74, 3, 155, 2, 75, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10,
19, 11, 21, 12, 23, 13, 25, 14, 27, 15, 29, 16, 31, 17, 33, 18, 35, 19,
37, 20, 39, 21, 41, 22, 43, 23, 45, 24, 47, 25, 49, 26, 51, 27, 53, 28,
55, 29, 57, 30, 59, 31, 61, 32, 63, 33, 65, 34, 67, 35, 69, 36, 71, 37,
73, 38, 75, 39, 77, 40, 79, 41, 81, 42, 83, 43, 85, 44, 87, 45, 89, 46,
91, 47, 93, 48, 95, 49, 97, 50, 99, 51, 101, 52, 103, 53, 105, 54, 107,
55, 109, 56, 111, 57, 113, 58, 115, 59, 117, 60, 119, 61, 121, 62, 123,
63, 125, 64, 127, 2, 129, 2, 131, 2, 133, 2, 135, 2, 137, 2, 139, 2, 141,
2, 143, 2, 145, 2, 147, 2, 3, 2, 14, 5, 2, 12, 12, 15, 15, 8234, 8235,
6, 2, 11, 11, 13, 14, 34, 34, 162, 162, 3, 2, 50, 59, 5, 2, 50, 59, 67,
72, 99, 104, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47,
4, 2, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2,
98, 98, 3, 2, 182, 182, 2, 569, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2,
7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2,
2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2,
2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2,
@@ -96,160 +98,166 @@ var serializedLexerAtn = []uint16{
99, 3, 2, 2, 2, 2, 101, 3, 2, 2, 2, 2, 103, 3, 2, 2, 2, 2, 105, 3, 2, 2,
2, 2, 107, 3, 2, 2, 2, 2, 109, 3, 2, 2, 2, 2, 111, 3, 2, 2, 2, 2, 113,
3, 2, 2, 2, 2, 115, 3, 2, 2, 2, 2, 117, 3, 2, 2, 2, 2, 119, 3, 2, 2, 2,
2, 121, 3, 2, 2, 2, 2, 123, 3, 2, 2, 2, 2, 125, 3, 2, 2, 2, 2, 127, 3,
2, 2, 2, 3, 147, 3, 2, 2, 2, 5, 161, 3, 2, 2, 2, 7, 173, 3, 2, 2, 2, 9,
179, 3, 2, 2, 2, 11, 183, 3, 2, 2, 2, 13, 185, 3, 2, 2, 2, 15, 187, 3,
2, 2, 2, 17, 189, 3, 2, 2, 2, 19, 191, 3, 2, 2, 2, 21, 193, 3, 2, 2, 2,
23, 195, 3, 2, 2, 2, 25, 197, 3, 2, 2, 2, 27, 199, 3, 2, 2, 2, 29, 201,
3, 2, 2, 2, 31, 203, 3, 2, 2, 2, 33, 205, 3, 2, 2, 2, 35, 207, 3, 2, 2,
2, 37, 210, 3, 2, 2, 2, 39, 213, 3, 2, 2, 2, 41, 216, 3, 2, 2, 2, 43, 219,
3, 2, 2, 2, 45, 221, 3, 2, 2, 2, 47, 223, 3, 2, 2, 2, 49, 225, 3, 2, 2,
2, 51, 227, 3, 2, 2, 2, 53, 229, 3, 2, 2, 2, 55, 232, 3, 2, 2, 2, 57, 240,
3, 2, 2, 2, 59, 246, 3, 2, 2, 2, 61, 248, 3, 2, 2, 2, 63, 251, 3, 2, 2,
2, 65, 253, 3, 2, 2, 2, 67, 255, 3, 2, 2, 2, 69, 258, 3, 2, 2, 2, 71, 261,
3, 2, 2, 2, 73, 265, 3, 2, 2, 2, 75, 272, 3, 2, 2, 2, 77, 281, 3, 2, 2,
2, 79, 288, 3, 2, 2, 2, 81, 293, 3, 2, 2, 2, 83, 299, 3, 2, 2, 2, 85, 303,
3, 2, 2, 2, 87, 318, 3, 2, 2, 2, 89, 320, 3, 2, 2, 2, 91, 325, 3, 2, 2,
2, 93, 348, 3, 2, 2, 2, 95, 350, 3, 2, 2, 2, 97, 355, 3, 2, 2, 2, 99, 360,
3, 2, 2, 2, 101, 365, 3, 2, 2, 2, 103, 371, 3, 2, 2, 2, 105, 375, 3, 2,
2, 2, 107, 379, 3, 2, 2, 2, 109, 389, 3, 2, 2, 2, 111, 398, 3, 2, 2, 2,
113, 400, 3, 2, 2, 2, 115, 403, 3, 2, 2, 2, 117, 406, 3, 2, 2, 2, 119,
436, 3, 2, 2, 2, 121, 438, 3, 2, 2, 2, 123, 450, 3, 2, 2, 2, 125, 468,
3, 2, 2, 2, 127, 470, 3, 2, 2, 2, 129, 473, 3, 2, 2, 2, 131, 483, 3, 2,
2, 2, 133, 485, 3, 2, 2, 2, 135, 494, 3, 2, 2, 2, 137, 496, 3, 2, 2, 2,
139, 498, 3, 2, 2, 2, 141, 500, 3, 2, 2, 2, 143, 513, 3, 2, 2, 2, 145,
526, 3, 2, 2, 2, 147, 148, 7, 49, 2, 2, 148, 149, 7, 44, 2, 2, 149, 153,
3, 2, 2, 2, 150, 152, 11, 2, 2, 2, 151, 150, 3, 2, 2, 2, 152, 155, 3, 2,
2, 2, 153, 154, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 156, 3, 2, 2, 2,
155, 153, 3, 2, 2, 2, 156, 157, 7, 44, 2, 2, 157, 158, 7, 49, 2, 2, 158,
159, 3, 2, 2, 2, 159, 160, 8, 2, 2, 2, 160, 4, 3, 2, 2, 2, 161, 162, 7,
49, 2, 2, 162, 163, 7, 49, 2, 2, 163, 167, 3, 2, 2, 2, 164, 166, 10, 2,
2, 2, 165, 164, 3, 2, 2, 2, 166, 169, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2,
167, 168, 3, 2, 2, 2, 168, 170, 3, 2, 2, 2, 169, 167, 3, 2, 2, 2, 170,
171, 8, 3, 2, 2, 171, 6, 3, 2, 2, 2, 172, 174, 9, 3, 2, 2, 173, 172, 3,
2, 2, 2, 174, 175, 3, 2, 2, 2, 175, 173, 3, 2, 2, 2, 175, 176, 3, 2, 2,
2, 176, 177, 3, 2, 2, 2, 177, 178, 8, 4, 2, 2, 178, 8, 3, 2, 2, 2, 179,
180, 9, 2, 2, 2, 180, 181, 3, 2, 2, 2, 181, 182, 8, 5, 2, 2, 182, 10, 3,
2, 2, 2, 183, 184, 7, 60, 2, 2, 184, 12, 3, 2, 2, 2, 185, 186, 7, 61, 2,
2, 186, 14, 3, 2, 2, 2, 187, 188, 7, 48, 2, 2, 188, 16, 3, 2, 2, 2, 189,
190, 7, 46, 2, 2, 190, 18, 3, 2, 2, 2, 191, 192, 7, 93, 2, 2, 192, 20,
3, 2, 2, 2, 193, 194, 7, 95, 2, 2, 194, 22, 3, 2, 2, 2, 195, 196, 7, 42,
2, 2, 196, 24, 3, 2, 2, 2, 197, 198, 7, 43, 2, 2, 198, 26, 3, 2, 2, 2,
199, 200, 7, 125, 2, 2, 200, 28, 3, 2, 2, 2, 201, 202, 7, 127, 2, 2, 202,
30, 3, 2, 2, 2, 203, 204, 7, 64, 2, 2, 204, 32, 3, 2, 2, 2, 205, 206, 7,
62, 2, 2, 206, 34, 3, 2, 2, 2, 207, 208, 7, 63, 2, 2, 208, 209, 7, 63,
2, 2, 209, 36, 3, 2, 2, 2, 210, 211, 7, 64, 2, 2, 211, 212, 7, 63, 2, 2,
212, 38, 3, 2, 2, 2, 213, 214, 7, 62, 2, 2, 214, 215, 7, 63, 2, 2, 215,
40, 3, 2, 2, 2, 216, 217, 7, 35, 2, 2, 217, 218, 7, 63, 2, 2, 218, 42,
3, 2, 2, 2, 219, 220, 7, 44, 2, 2, 220, 44, 3, 2, 2, 2, 221, 222, 7, 49,
2, 2, 222, 46, 3, 2, 2, 2, 223, 224, 7, 39, 2, 2, 224, 48, 3, 2, 2, 2,
225, 226, 7, 45, 2, 2, 226, 50, 3, 2, 2, 2, 227, 228, 7, 47, 2, 2, 228,
52, 3, 2, 2, 2, 229, 230, 7, 47, 2, 2, 230, 231, 7, 47, 2, 2, 231, 54,
3, 2, 2, 2, 232, 233, 7, 45, 2, 2, 233, 234, 7, 45, 2, 2, 234, 56, 3, 2,
2, 2, 235, 236, 7, 67, 2, 2, 236, 237, 7, 80, 2, 2, 237, 241, 7, 70, 2,
2, 238, 239, 7, 40, 2, 2, 239, 241, 7, 40, 2, 2, 240, 235, 3, 2, 2, 2,
240, 238, 3, 2, 2, 2, 241, 58, 3, 2, 2, 2, 242, 243, 7, 81, 2, 2, 243,
247, 7, 84, 2, 2, 244, 245, 7, 126, 2, 2, 245, 247, 7, 126, 2, 2, 246,
242, 3, 2, 2, 2, 246, 244, 3, 2, 2, 2, 247, 60, 3, 2, 2, 2, 248, 249, 5,
15, 8, 2, 249, 250, 5, 15, 8, 2, 250, 62, 3, 2, 2, 2, 251, 252, 7, 63,
2, 2, 252, 64, 3, 2, 2, 2, 253, 254, 7, 65, 2, 2, 254, 66, 3, 2, 2, 2,
255, 256, 7, 35, 2, 2, 256, 257, 7, 128, 2, 2, 257, 68, 3, 2, 2, 2, 258,
259, 7, 63, 2, 2, 259, 260, 7, 128, 2, 2, 260, 70, 3, 2, 2, 2, 261, 262,
7, 72, 2, 2, 262, 263, 7, 81, 2, 2, 263, 264, 7, 84, 2, 2, 264, 72, 3,
2, 2, 2, 265, 266, 7, 84, 2, 2, 266, 267, 7, 71, 2, 2, 267, 268, 7, 86,
2, 2, 268, 269, 7, 87, 2, 2, 269, 270, 7, 84, 2, 2, 270, 271, 7, 80, 2,
2, 271, 74, 3, 2, 2, 2, 272, 273, 7, 70, 2, 2, 273, 274, 7, 75, 2, 2, 274,
275, 7, 85, 2, 2, 275, 276, 7, 86, 2, 2, 276, 277, 7, 75, 2, 2, 277, 278,
7, 80, 2, 2, 278, 279, 7, 69, 2, 2, 279, 280, 7, 86, 2, 2, 280, 76, 3,
2, 2, 2, 281, 282, 7, 72, 2, 2, 282, 283, 7, 75, 2, 2, 283, 284, 7, 78,
2, 2, 284, 285, 7, 86, 2, 2, 285, 286, 7, 71, 2, 2, 286, 287, 7, 84, 2,
2, 287, 78, 3, 2, 2, 2, 288, 289, 7, 85, 2, 2, 289, 290, 7, 81, 2, 2, 290,
291, 7, 84, 2, 2, 291, 292, 7, 86, 2, 2, 292, 80, 3, 2, 2, 2, 293, 294,
7, 78, 2, 2, 294, 295, 7, 75, 2, 2, 295, 296, 7, 79, 2, 2, 296, 297, 7,
75, 2, 2, 297, 298, 7, 86, 2, 2, 298, 82, 3, 2, 2, 2, 299, 300, 7, 78,
2, 2, 300, 301, 7, 71, 2, 2, 301, 302, 7, 86, 2, 2, 302, 84, 3, 2, 2, 2,
303, 304, 7, 69, 2, 2, 304, 305, 7, 81, 2, 2, 305, 306, 7, 78, 2, 2, 306,
307, 7, 78, 2, 2, 307, 308, 7, 71, 2, 2, 308, 309, 7, 69, 2, 2, 309, 310,
7, 86, 2, 2, 310, 86, 3, 2, 2, 2, 311, 312, 7, 67, 2, 2, 312, 313, 7, 85,
2, 2, 313, 319, 7, 69, 2, 2, 314, 315, 7, 70, 2, 2, 315, 316, 7, 71, 2,
2, 316, 317, 7, 85, 2, 2, 317, 319, 7, 69, 2, 2, 318, 311, 3, 2, 2, 2,
318, 314, 3, 2, 2, 2, 319, 88, 3, 2, 2, 2, 320, 321, 7, 80, 2, 2, 321,
322, 7, 81, 2, 2, 322, 323, 7, 80, 2, 2, 323, 324, 7, 71, 2, 2, 324, 90,
3, 2, 2, 2, 325, 326, 7, 80, 2, 2, 326, 327, 7, 87, 2, 2, 327, 328, 7,
78, 2, 2, 328, 329, 7, 78, 2, 2, 329, 92, 3, 2, 2, 2, 330, 331, 7, 86,
2, 2, 331, 332, 7, 84, 2, 2, 332, 333, 7, 87, 2, 2, 333, 349, 7, 71, 2,
2, 334, 335, 7, 118, 2, 2, 335, 336, 7, 116, 2, 2, 336, 337, 7, 119, 2,
2, 337, 349, 7, 103, 2, 2, 338, 339, 7, 72, 2, 2, 339, 340, 7, 67, 2, 2,
340, 341, 7, 78, 2, 2, 341, 342, 7, 85, 2, 2, 342, 349, 7, 71, 2, 2, 343,
344, 7, 104, 2, 2, 344, 345, 7, 99, 2, 2, 345, 346, 7, 110, 2, 2, 346,
347, 7, 117, 2, 2, 347, 349, 7, 103, 2, 2, 348, 330, 3, 2, 2, 2, 348, 334,
3, 2, 2, 2, 348, 338, 3, 2, 2, 2, 348, 343, 3, 2, 2, 2, 349, 94, 3, 2,
2, 2, 350, 351, 7, 75, 2, 2, 351, 352, 7, 80, 2, 2, 352, 353, 7, 86, 2,
2, 353, 354, 7, 81, 2, 2, 354, 96, 3, 2, 2, 2, 355, 356, 7, 77, 2, 2, 356,
357, 7, 71, 2, 2, 357, 358, 7, 71, 2, 2, 358, 359, 7, 82, 2, 2, 359, 98,
3, 2, 2, 2, 360, 361, 7, 89, 2, 2, 361, 362, 7, 75, 2, 2, 362, 363, 7,
86, 2, 2, 363, 364, 7, 74, 2, 2, 364, 100, 3, 2, 2, 2, 365, 366, 7, 69,
2, 2, 366, 367, 7, 81, 2, 2, 367, 368, 7, 87, 2, 2, 368, 369, 7, 80, 2,
2, 369, 370, 7, 86, 2, 2, 370, 102, 3, 2, 2, 2, 371, 372, 7, 67, 2, 2,
372, 373, 7, 78, 2, 2, 373, 374, 7, 78, 2, 2, 374, 104, 3, 2, 2, 2, 375,
376, 7, 67, 2, 2, 376, 377, 7, 80, 2, 2, 377, 378, 7, 91, 2, 2, 378, 106,
3, 2, 2, 2, 379, 380, 7, 67, 2, 2, 380, 381, 7, 73, 2, 2, 381, 382, 7,
73, 2, 2, 382, 383, 7, 84, 2, 2, 383, 384, 7, 71, 2, 2, 384, 385, 7, 73,
2, 2, 385, 386, 7, 67, 2, 2, 386, 387, 7, 86, 2, 2, 387, 388, 7, 71, 2,
2, 388, 108, 3, 2, 2, 2, 389, 390, 7, 78, 2, 2, 390, 391, 7, 75, 2, 2,
391, 392, 7, 77, 2, 2, 392, 393, 7, 71, 2, 2, 393, 110, 3, 2, 2, 2, 394,
395, 7, 80, 2, 2, 395, 396, 7, 81, 2, 2, 396, 399, 7, 86, 2, 2, 397, 399,
7, 35, 2, 2, 398, 394, 3, 2, 2, 2, 398, 397, 3, 2, 2, 2, 399, 112, 3, 2,
2, 2, 400, 401, 7, 75, 2, 2, 401, 402, 7, 80, 2, 2, 402, 114, 3, 2, 2,
2, 403, 404, 7, 66, 2, 2, 404, 116, 3, 2, 2, 2, 405, 407, 5, 135, 68, 2,
406, 405, 3, 2, 2, 2, 407, 408, 3, 2, 2, 2, 408, 406, 3, 2, 2, 2, 408,
409, 3, 2, 2, 2, 409, 419, 3, 2, 2, 2, 410, 414, 5, 137, 69, 2, 411, 413,
5, 117, 59, 2, 412, 411, 3, 2, 2, 2, 413, 416, 3, 2, 2, 2, 414, 412, 3,
2, 2, 2, 414, 415, 3, 2, 2, 2, 415, 418, 3, 2, 2, 2, 416, 414, 3, 2, 2,
2, 417, 410, 3, 2, 2, 2, 418, 421, 3, 2, 2, 2, 419, 417, 3, 2, 2, 2, 419,
420, 3, 2, 2, 2, 420, 431, 3, 2, 2, 2, 421, 419, 3, 2, 2, 2, 422, 426,
5, 139, 70, 2, 423, 425, 5, 117, 59, 2, 424, 423, 3, 2, 2, 2, 425, 428,
3, 2, 2, 2, 426, 424, 3, 2, 2, 2, 426, 427, 3, 2, 2, 2, 427, 430, 3, 2,
2, 2, 428, 426, 3, 2, 2, 2, 429, 422, 3, 2, 2, 2, 430, 433, 3, 2, 2, 2,
431, 429, 3, 2, 2, 2, 431, 432, 3, 2, 2, 2, 432, 118, 3, 2, 2, 2, 433,
431, 3, 2, 2, 2, 434, 437, 5, 143, 72, 2, 435, 437, 5, 141, 71, 2, 436,
434, 3, 2, 2, 2, 436, 435, 3, 2, 2, 2, 437, 120, 3, 2, 2, 2, 438, 444,
7, 98, 2, 2, 439, 440, 7, 94, 2, 2, 440, 443, 7, 98, 2, 2, 441, 443, 10,
4, 2, 2, 442, 439, 3, 2, 2, 2, 442, 441, 3, 2, 2, 2, 443, 446, 3, 2, 2,
2, 444, 442, 3, 2, 2, 2, 444, 445, 3, 2, 2, 2, 445, 447, 3, 2, 2, 2, 446,
444, 3, 2, 2, 2, 447, 448, 7, 98, 2, 2, 448, 122, 3, 2, 2, 2, 449, 451,
9, 5, 2, 2, 450, 449, 3, 2, 2, 2, 451, 452, 3, 2, 2, 2, 452, 450, 3, 2,
2, 2, 452, 453, 3, 2, 2, 2, 453, 124, 3, 2, 2, 2, 454, 455, 5, 131, 66,
2, 455, 457, 5, 15, 8, 2, 456, 458, 9, 5, 2, 2, 457, 456, 3, 2, 2, 2, 458,
459, 3, 2, 2, 2, 459, 457, 3, 2, 2, 2, 459, 460, 3, 2, 2, 2, 460, 462,
3, 2, 2, 2, 461, 463, 5, 133, 67, 2, 462, 461, 3, 2, 2, 2, 462, 463, 3,
2, 2, 2, 463, 469, 3, 2, 2, 2, 464, 466, 5, 131, 66, 2, 465, 467, 5, 133,
67, 2, 466, 465, 3, 2, 2, 2, 466, 467, 3, 2, 2, 2, 467, 469, 3, 2, 2, 2,
468, 454, 3, 2, 2, 2, 468, 464, 3, 2, 2, 2, 469, 126, 3, 2, 2, 2, 470,
471, 5, 117, 59, 2, 471, 472, 5, 145, 73, 2, 472, 128, 3, 2, 2, 2, 473,
474, 9, 6, 2, 2, 474, 130, 3, 2, 2, 2, 475, 484, 7, 50, 2, 2, 476, 480,
9, 7, 2, 2, 477, 479, 9, 5, 2, 2, 478, 477, 3, 2, 2, 2, 479, 482, 3, 2,
2, 2, 480, 478, 3, 2, 2, 2, 480, 481, 3, 2, 2, 2, 481, 484, 3, 2, 2, 2,
482, 480, 3, 2, 2, 2, 483, 475, 3, 2, 2, 2, 483, 476, 3, 2, 2, 2, 484,
132, 3, 2, 2, 2, 485, 487, 9, 8, 2, 2, 486, 488, 9, 9, 2, 2, 487, 486,
3, 2, 2, 2, 487, 488, 3, 2, 2, 2, 488, 490, 3, 2, 2, 2, 489, 491, 9, 5,
2, 2, 490, 489, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 490, 3, 2, 2, 2,
492, 493, 3, 2, 2, 2, 493, 134, 3, 2, 2, 2, 494, 495, 9, 10, 2, 2, 495,
136, 3, 2, 2, 2, 496, 497, 7, 97, 2, 2, 497, 138, 3, 2, 2, 2, 498, 499,
4, 50, 59, 2, 499, 140, 3, 2, 2, 2, 500, 508, 7, 36, 2, 2, 501, 502, 7,
94, 2, 2, 502, 507, 11, 2, 2, 2, 503, 504, 7, 36, 2, 2, 504, 507, 7, 36,
2, 2, 505, 507, 10, 11, 2, 2, 506, 501, 3, 2, 2, 2, 506, 503, 3, 2, 2,
2, 506, 505, 3, 2, 2, 2, 507, 510, 3, 2, 2, 2, 508, 506, 3, 2, 2, 2, 508,
509, 3, 2, 2, 2, 509, 511, 3, 2, 2, 2, 510, 508, 3, 2, 2, 2, 511, 512,
7, 36, 2, 2, 512, 142, 3, 2, 2, 2, 513, 521, 7, 41, 2, 2, 514, 515, 7,
94, 2, 2, 515, 520, 11, 2, 2, 2, 516, 517, 7, 41, 2, 2, 517, 520, 7, 41,
2, 2, 518, 520, 10, 12, 2, 2, 519, 514, 3, 2, 2, 2, 519, 516, 3, 2, 2,
2, 519, 518, 3, 2, 2, 2, 520, 523, 3, 2, 2, 2, 521, 519, 3, 2, 2, 2, 521,
522, 3, 2, 2, 2, 522, 524, 3, 2, 2, 2, 523, 521, 3, 2, 2, 2, 524, 525,
7, 41, 2, 2, 525, 144, 3, 2, 2, 2, 526, 527, 7, 60, 2, 2, 527, 528, 7,
60, 2, 2, 528, 146, 3, 2, 2, 2, 32, 2, 153, 167, 175, 240, 246, 318, 348,
398, 408, 414, 419, 426, 431, 436, 442, 444, 452, 459, 462, 466, 468, 480,
483, 487, 492, 506, 508, 519, 521, 3, 2, 3, 2,
2, 121, 3, 2, 2, 2, 2, 123, 3, 2, 2, 2, 2, 125, 3, 2, 2, 2, 3, 149, 3,
2, 2, 2, 5, 163, 3, 2, 2, 2, 7, 175, 3, 2, 2, 2, 9, 181, 3, 2, 2, 2, 11,
185, 3, 2, 2, 2, 13, 187, 3, 2, 2, 2, 15, 189, 3, 2, 2, 2, 17, 191, 3,
2, 2, 2, 19, 193, 3, 2, 2, 2, 21, 195, 3, 2, 2, 2, 23, 197, 3, 2, 2, 2,
25, 199, 3, 2, 2, 2, 27, 201, 3, 2, 2, 2, 29, 203, 3, 2, 2, 2, 31, 205,
3, 2, 2, 2, 33, 207, 3, 2, 2, 2, 35, 209, 3, 2, 2, 2, 37, 212, 3, 2, 2,
2, 39, 215, 3, 2, 2, 2, 41, 218, 3, 2, 2, 2, 43, 221, 3, 2, 2, 2, 45, 223,
3, 2, 2, 2, 47, 225, 3, 2, 2, 2, 49, 227, 3, 2, 2, 2, 51, 229, 3, 2, 2,
2, 53, 231, 3, 2, 2, 2, 55, 234, 3, 2, 2, 2, 57, 242, 3, 2, 2, 2, 59, 248,
3, 2, 2, 2, 61, 250, 3, 2, 2, 2, 63, 253, 3, 2, 2, 2, 65, 255, 3, 2, 2,
2, 67, 257, 3, 2, 2, 2, 69, 260, 3, 2, 2, 2, 71, 263, 3, 2, 2, 2, 73, 267,
3, 2, 2, 2, 75, 274, 3, 2, 2, 2, 77, 283, 3, 2, 2, 2, 79, 290, 3, 2, 2,
2, 81, 295, 3, 2, 2, 2, 83, 301, 3, 2, 2, 2, 85, 305, 3, 2, 2, 2, 87, 320,
3, 2, 2, 2, 89, 322, 3, 2, 2, 2, 91, 327, 3, 2, 2, 2, 93, 350, 3, 2, 2,
2, 95, 352, 3, 2, 2, 2, 97, 357, 3, 2, 2, 2, 99, 362, 3, 2, 2, 2, 101,
367, 3, 2, 2, 2, 103, 373, 3, 2, 2, 2, 105, 377, 3, 2, 2, 2, 107, 381,
3, 2, 2, 2, 109, 391, 3, 2, 2, 2, 111, 400, 3, 2, 2, 2, 113, 402, 3, 2,
2, 2, 115, 405, 3, 2, 2, 2, 117, 408, 3, 2, 2, 2, 119, 440, 3, 2, 2, 2,
121, 443, 3, 2, 2, 2, 123, 461, 3, 2, 2, 2, 125, 463, 3, 2, 2, 2, 127,
466, 3, 2, 2, 2, 129, 476, 3, 2, 2, 2, 131, 478, 3, 2, 2, 2, 133, 487,
3, 2, 2, 2, 135, 489, 3, 2, 2, 2, 137, 491, 3, 2, 2, 2, 139, 493, 3, 2,
2, 2, 141, 506, 3, 2, 2, 2, 143, 519, 3, 2, 2, 2, 145, 530, 3, 2, 2, 2,
147, 541, 3, 2, 2, 2, 149, 150, 7, 49, 2, 2, 150, 151, 7, 44, 2, 2, 151,
155, 3, 2, 2, 2, 152, 154, 11, 2, 2, 2, 153, 152, 3, 2, 2, 2, 154, 157,
3, 2, 2, 2, 155, 156, 3, 2, 2, 2, 155, 153, 3, 2, 2, 2, 156, 158, 3, 2,
2, 2, 157, 155, 3, 2, 2, 2, 158, 159, 7, 44, 2, 2, 159, 160, 7, 49, 2,
2, 160, 161, 3, 2, 2, 2, 161, 162, 8, 2, 2, 2, 162, 4, 3, 2, 2, 2, 163,
164, 7, 49, 2, 2, 164, 165, 7, 49, 2, 2, 165, 169, 3, 2, 2, 2, 166, 168,
10, 2, 2, 2, 167, 166, 3, 2, 2, 2, 168, 171, 3, 2, 2, 2, 169, 167, 3, 2,
2, 2, 169, 170, 3, 2, 2, 2, 170, 172, 3, 2, 2, 2, 171, 169, 3, 2, 2, 2,
172, 173, 8, 3, 2, 2, 173, 6, 3, 2, 2, 2, 174, 176, 9, 3, 2, 2, 175, 174,
3, 2, 2, 2, 176, 177, 3, 2, 2, 2, 177, 175, 3, 2, 2, 2, 177, 178, 3, 2,
2, 2, 178, 179, 3, 2, 2, 2, 179, 180, 8, 4, 2, 2, 180, 8, 3, 2, 2, 2, 181,
182, 9, 2, 2, 2, 182, 183, 3, 2, 2, 2, 183, 184, 8, 5, 2, 2, 184, 10, 3,
2, 2, 2, 185, 186, 7, 60, 2, 2, 186, 12, 3, 2, 2, 2, 187, 188, 7, 61, 2,
2, 188, 14, 3, 2, 2, 2, 189, 190, 7, 48, 2, 2, 190, 16, 3, 2, 2, 2, 191,
192, 7, 46, 2, 2, 192, 18, 3, 2, 2, 2, 193, 194, 7, 93, 2, 2, 194, 20,
3, 2, 2, 2, 195, 196, 7, 95, 2, 2, 196, 22, 3, 2, 2, 2, 197, 198, 7, 42,
2, 2, 198, 24, 3, 2, 2, 2, 199, 200, 7, 43, 2, 2, 200, 26, 3, 2, 2, 2,
201, 202, 7, 125, 2, 2, 202, 28, 3, 2, 2, 2, 203, 204, 7, 127, 2, 2, 204,
30, 3, 2, 2, 2, 205, 206, 7, 64, 2, 2, 206, 32, 3, 2, 2, 2, 207, 208, 7,
62, 2, 2, 208, 34, 3, 2, 2, 2, 209, 210, 7, 63, 2, 2, 210, 211, 7, 63,
2, 2, 211, 36, 3, 2, 2, 2, 212, 213, 7, 64, 2, 2, 213, 214, 7, 63, 2, 2,
214, 38, 3, 2, 2, 2, 215, 216, 7, 62, 2, 2, 216, 217, 7, 63, 2, 2, 217,
40, 3, 2, 2, 2, 218, 219, 7, 35, 2, 2, 219, 220, 7, 63, 2, 2, 220, 42,
3, 2, 2, 2, 221, 222, 7, 44, 2, 2, 222, 44, 3, 2, 2, 2, 223, 224, 7, 49,
2, 2, 224, 46, 3, 2, 2, 2, 225, 226, 7, 39, 2, 2, 226, 48, 3, 2, 2, 2,
227, 228, 7, 45, 2, 2, 228, 50, 3, 2, 2, 2, 229, 230, 7, 47, 2, 2, 230,
52, 3, 2, 2, 2, 231, 232, 7, 47, 2, 2, 232, 233, 7, 47, 2, 2, 233, 54,
3, 2, 2, 2, 234, 235, 7, 45, 2, 2, 235, 236, 7, 45, 2, 2, 236, 56, 3, 2,
2, 2, 237, 238, 7, 67, 2, 2, 238, 239, 7, 80, 2, 2, 239, 243, 7, 70, 2,
2, 240, 241, 7, 40, 2, 2, 241, 243, 7, 40, 2, 2, 242, 237, 3, 2, 2, 2,
242, 240, 3, 2, 2, 2, 243, 58, 3, 2, 2, 2, 244, 245, 7, 81, 2, 2, 245,
249, 7, 84, 2, 2, 246, 247, 7, 126, 2, 2, 247, 249, 7, 126, 2, 2, 248,
244, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 249, 60, 3, 2, 2, 2, 250, 251, 5,
15, 8, 2, 251, 252, 5, 15, 8, 2, 252, 62, 3, 2, 2, 2, 253, 254, 7, 63,
2, 2, 254, 64, 3, 2, 2, 2, 255, 256, 7, 65, 2, 2, 256, 66, 3, 2, 2, 2,
257, 258, 7, 35, 2, 2, 258, 259, 7, 128, 2, 2, 259, 68, 3, 2, 2, 2, 260,
261, 7, 63, 2, 2, 261, 262, 7, 128, 2, 2, 262, 70, 3, 2, 2, 2, 263, 264,
7, 72, 2, 2, 264, 265, 7, 81, 2, 2, 265, 266, 7, 84, 2, 2, 266, 72, 3,
2, 2, 2, 267, 268, 7, 84, 2, 2, 268, 269, 7, 71, 2, 2, 269, 270, 7, 86,
2, 2, 270, 271, 7, 87, 2, 2, 271, 272, 7, 84, 2, 2, 272, 273, 7, 80, 2,
2, 273, 74, 3, 2, 2, 2, 274, 275, 7, 70, 2, 2, 275, 276, 7, 75, 2, 2, 276,
277, 7, 85, 2, 2, 277, 278, 7, 86, 2, 2, 278, 279, 7, 75, 2, 2, 279, 280,
7, 80, 2, 2, 280, 281, 7, 69, 2, 2, 281, 282, 7, 86, 2, 2, 282, 76, 3,
2, 2, 2, 283, 284, 7, 72, 2, 2, 284, 285, 7, 75, 2, 2, 285, 286, 7, 78,
2, 2, 286, 287, 7, 86, 2, 2, 287, 288, 7, 71, 2, 2, 288, 289, 7, 84, 2,
2, 289, 78, 3, 2, 2, 2, 290, 291, 7, 85, 2, 2, 291, 292, 7, 81, 2, 2, 292,
293, 7, 84, 2, 2, 293, 294, 7, 86, 2, 2, 294, 80, 3, 2, 2, 2, 295, 296,
7, 78, 2, 2, 296, 297, 7, 75, 2, 2, 297, 298, 7, 79, 2, 2, 298, 299, 7,
75, 2, 2, 299, 300, 7, 86, 2, 2, 300, 82, 3, 2, 2, 2, 301, 302, 7, 78,
2, 2, 302, 303, 7, 71, 2, 2, 303, 304, 7, 86, 2, 2, 304, 84, 3, 2, 2, 2,
305, 306, 7, 69, 2, 2, 306, 307, 7, 81, 2, 2, 307, 308, 7, 78, 2, 2, 308,
309, 7, 78, 2, 2, 309, 310, 7, 71, 2, 2, 310, 311, 7, 69, 2, 2, 311, 312,
7, 86, 2, 2, 312, 86, 3, 2, 2, 2, 313, 314, 7, 67, 2, 2, 314, 315, 7, 85,
2, 2, 315, 321, 7, 69, 2, 2, 316, 317, 7, 70, 2, 2, 317, 318, 7, 71, 2,
2, 318, 319, 7, 85, 2, 2, 319, 321, 7, 69, 2, 2, 320, 313, 3, 2, 2, 2,
320, 316, 3, 2, 2, 2, 321, 88, 3, 2, 2, 2, 322, 323, 7, 80, 2, 2, 323,
324, 7, 81, 2, 2, 324, 325, 7, 80, 2, 2, 325, 326, 7, 71, 2, 2, 326, 90,
3, 2, 2, 2, 327, 328, 7, 80, 2, 2, 328, 329, 7, 87, 2, 2, 329, 330, 7,
78, 2, 2, 330, 331, 7, 78, 2, 2, 331, 92, 3, 2, 2, 2, 332, 333, 7, 86,
2, 2, 333, 334, 7, 84, 2, 2, 334, 335, 7, 87, 2, 2, 335, 351, 7, 71, 2,
2, 336, 337, 7, 118, 2, 2, 337, 338, 7, 116, 2, 2, 338, 339, 7, 119, 2,
2, 339, 351, 7, 103, 2, 2, 340, 341, 7, 72, 2, 2, 341, 342, 7, 67, 2, 2,
342, 343, 7, 78, 2, 2, 343, 344, 7, 85, 2, 2, 344, 351, 7, 71, 2, 2, 345,
346, 7, 104, 2, 2, 346, 347, 7, 99, 2, 2, 347, 348, 7, 110, 2, 2, 348,
349, 7, 117, 2, 2, 349, 351, 7, 103, 2, 2, 350, 332, 3, 2, 2, 2, 350, 336,
3, 2, 2, 2, 350, 340, 3, 2, 2, 2, 350, 345, 3, 2, 2, 2, 351, 94, 3, 2,
2, 2, 352, 353, 7, 75, 2, 2, 353, 354, 7, 80, 2, 2, 354, 355, 7, 86, 2,
2, 355, 356, 7, 81, 2, 2, 356, 96, 3, 2, 2, 2, 357, 358, 7, 77, 2, 2, 358,
359, 7, 71, 2, 2, 359, 360, 7, 71, 2, 2, 360, 361, 7, 82, 2, 2, 361, 98,
3, 2, 2, 2, 362, 363, 7, 89, 2, 2, 363, 364, 7, 75, 2, 2, 364, 365, 7,
86, 2, 2, 365, 366, 7, 74, 2, 2, 366, 100, 3, 2, 2, 2, 367, 368, 7, 69,
2, 2, 368, 369, 7, 81, 2, 2, 369, 370, 7, 87, 2, 2, 370, 371, 7, 80, 2,
2, 371, 372, 7, 86, 2, 2, 372, 102, 3, 2, 2, 2, 373, 374, 7, 67, 2, 2,
374, 375, 7, 78, 2, 2, 375, 376, 7, 78, 2, 2, 376, 104, 3, 2, 2, 2, 377,
378, 7, 67, 2, 2, 378, 379, 7, 80, 2, 2, 379, 380, 7, 91, 2, 2, 380, 106,
3, 2, 2, 2, 381, 382, 7, 67, 2, 2, 382, 383, 7, 73, 2, 2, 383, 384, 7,
73, 2, 2, 384, 385, 7, 84, 2, 2, 385, 386, 7, 71, 2, 2, 386, 387, 7, 73,
2, 2, 387, 388, 7, 67, 2, 2, 388, 389, 7, 86, 2, 2, 389, 390, 7, 71, 2,
2, 390, 108, 3, 2, 2, 2, 391, 392, 7, 78, 2, 2, 392, 393, 7, 75, 2, 2,
393, 394, 7, 77, 2, 2, 394, 395, 7, 71, 2, 2, 395, 110, 3, 2, 2, 2, 396,
397, 7, 80, 2, 2, 397, 398, 7, 81, 2, 2, 398, 401, 7, 86, 2, 2, 399, 401,
7, 35, 2, 2, 400, 396, 3, 2, 2, 2, 400, 399, 3, 2, 2, 2, 401, 112, 3, 2,
2, 2, 402, 403, 7, 75, 2, 2, 403, 404, 7, 80, 2, 2, 404, 114, 3, 2, 2,
2, 405, 406, 7, 66, 2, 2, 406, 116, 3, 2, 2, 2, 407, 409, 5, 133, 67, 2,
408, 407, 3, 2, 2, 2, 409, 410, 3, 2, 2, 2, 410, 408, 3, 2, 2, 2, 410,
411, 3, 2, 2, 2, 411, 421, 3, 2, 2, 2, 412, 416, 5, 135, 68, 2, 413, 415,
5, 117, 59, 2, 414, 413, 3, 2, 2, 2, 415, 418, 3, 2, 2, 2, 416, 414, 3,
2, 2, 2, 416, 417, 3, 2, 2, 2, 417, 420, 3, 2, 2, 2, 418, 416, 3, 2, 2,
2, 419, 412, 3, 2, 2, 2, 420, 423, 3, 2, 2, 2, 421, 419, 3, 2, 2, 2, 421,
422, 3, 2, 2, 2, 422, 433, 3, 2, 2, 2, 423, 421, 3, 2, 2, 2, 424, 428,
5, 137, 69, 2, 425, 427, 5, 117, 59, 2, 426, 425, 3, 2, 2, 2, 427, 430,
3, 2, 2, 2, 428, 426, 3, 2, 2, 2, 428, 429, 3, 2, 2, 2, 429, 432, 3, 2,
2, 2, 430, 428, 3, 2, 2, 2, 431, 424, 3, 2, 2, 2, 432, 435, 3, 2, 2, 2,
433, 431, 3, 2, 2, 2, 433, 434, 3, 2, 2, 2, 434, 118, 3, 2, 2, 2, 435,
433, 3, 2, 2, 2, 436, 441, 5, 141, 71, 2, 437, 441, 5, 139, 70, 2, 438,
441, 5, 143, 72, 2, 439, 441, 5, 145, 73, 2, 440, 436, 3, 2, 2, 2, 440,
437, 3, 2, 2, 2, 440, 438, 3, 2, 2, 2, 440, 439, 3, 2, 2, 2, 441, 120,
3, 2, 2, 2, 442, 444, 9, 4, 2, 2, 443, 442, 3, 2, 2, 2, 444, 445, 3, 2,
2, 2, 445, 443, 3, 2, 2, 2, 445, 446, 3, 2, 2, 2, 446, 122, 3, 2, 2, 2,
447, 448, 5, 129, 65, 2, 448, 450, 5, 15, 8, 2, 449, 451, 9, 4, 2, 2, 450,
449, 3, 2, 2, 2, 451, 452, 3, 2, 2, 2, 452, 450, 3, 2, 2, 2, 452, 453,
3, 2, 2, 2, 453, 455, 3, 2, 2, 2, 454, 456, 5, 131, 66, 2, 455, 454, 3,
2, 2, 2, 455, 456, 3, 2, 2, 2, 456, 462, 3, 2, 2, 2, 457, 459, 5, 129,
65, 2, 458, 460, 5, 131, 66, 2, 459, 458, 3, 2, 2, 2, 459, 460, 3, 2, 2,
2, 460, 462, 3, 2, 2, 2, 461, 447, 3, 2, 2, 2, 461, 457, 3, 2, 2, 2, 462,
124, 3, 2, 2, 2, 463, 464, 5, 117, 59, 2, 464, 465, 5, 147, 74, 2, 465,
126, 3, 2, 2, 2, 466, 467, 9, 5, 2, 2, 467, 128, 3, 2, 2, 2, 468, 477,
7, 50, 2, 2, 469, 473, 9, 6, 2, 2, 470, 472, 9, 4, 2, 2, 471, 470, 3, 2,
2, 2, 472, 475, 3, 2, 2, 2, 473, 471, 3, 2, 2, 2, 473, 474, 3, 2, 2, 2,
474, 477, 3, 2, 2, 2, 475, 473, 3, 2, 2, 2, 476, 468, 3, 2, 2, 2, 476,
469, 3, 2, 2, 2, 477, 130, 3, 2, 2, 2, 478, 480, 9, 7, 2, 2, 479, 481,
9, 8, 2, 2, 480, 479, 3, 2, 2, 2, 480, 481, 3, 2, 2, 2, 481, 483, 3, 2,
2, 2, 482, 484, 9, 4, 2, 2, 483, 482, 3, 2, 2, 2, 484, 485, 3, 2, 2, 2,
485, 483, 3, 2, 2, 2, 485, 486, 3, 2, 2, 2, 486, 132, 3, 2, 2, 2, 487,
488, 9, 9, 2, 2, 488, 134, 3, 2, 2, 2, 489, 490, 7, 97, 2, 2, 490, 136,
3, 2, 2, 2, 491, 492, 4, 50, 59, 2, 492, 138, 3, 2, 2, 2, 493, 501, 7,
36, 2, 2, 494, 495, 7, 94, 2, 2, 495, 500, 11, 2, 2, 2, 496, 497, 7, 36,
2, 2, 497, 500, 7, 36, 2, 2, 498, 500, 10, 10, 2, 2, 499, 494, 3, 2, 2,
2, 499, 496, 3, 2, 2, 2, 499, 498, 3, 2, 2, 2, 500, 503, 3, 2, 2, 2, 501,
499, 3, 2, 2, 2, 501, 502, 3, 2, 2, 2, 502, 504, 3, 2, 2, 2, 503, 501,
3, 2, 2, 2, 504, 505, 7, 36, 2, 2, 505, 140, 3, 2, 2, 2, 506, 514, 7, 41,
2, 2, 507, 508, 7, 94, 2, 2, 508, 513, 11, 2, 2, 2, 509, 510, 7, 41, 2,
2, 510, 513, 7, 41, 2, 2, 511, 513, 10, 11, 2, 2, 512, 507, 3, 2, 2, 2,
512, 509, 3, 2, 2, 2, 512, 511, 3, 2, 2, 2, 513, 516, 3, 2, 2, 2, 514,
512, 3, 2, 2, 2, 514, 515, 3, 2, 2, 2, 515, 517, 3, 2, 2, 2, 516, 514,
3, 2, 2, 2, 517, 518, 7, 41, 2, 2, 518, 142, 3, 2, 2, 2, 519, 525, 7, 98,
2, 2, 520, 521, 7, 94, 2, 2, 521, 524, 7, 98, 2, 2, 522, 524, 10, 12, 2,
2, 523, 520, 3, 2, 2, 2, 523, 522, 3, 2, 2, 2, 524, 527, 3, 2, 2, 2, 525,
523, 3, 2, 2, 2, 525, 526, 3, 2, 2, 2, 526, 528, 3, 2, 2, 2, 527, 525,
3, 2, 2, 2, 528, 529, 7, 98, 2, 2, 529, 144, 3, 2, 2, 2, 530, 536, 7, 182,
2, 2, 531, 532, 7, 94, 2, 2, 532, 535, 7, 182, 2, 2, 533, 535, 10, 13,
2, 2, 534, 531, 3, 2, 2, 2, 534, 533, 3, 2, 2, 2, 535, 538, 3, 2, 2, 2,
536, 534, 3, 2, 2, 2, 536, 537, 3, 2, 2, 2, 537, 539, 3, 2, 2, 2, 538,
536, 3, 2, 2, 2, 539, 540, 7, 182, 2, 2, 540, 146, 3, 2, 2, 2, 541, 542,
7, 60, 2, 2, 542, 543, 7, 60, 2, 2, 543, 148, 3, 2, 2, 2, 34, 2, 155, 169,
177, 242, 248, 320, 350, 400, 410, 416, 421, 428, 433, 440, 445, 452, 455,
459, 461, 473, 476, 480, 485, 499, 501, 512, 514, 523, 525, 534, 536, 3,
2, 3, 2,
}
var lexerDeserializer = antlr.NewATNDeserializer(nil)
@@ -281,8 +289,7 @@ var lexerSymbolicNames = []string{
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
"Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Param", "Identifier",
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
"NamespaceSegment",
"StringLiteral", "IntegerLiteral", "FloatLiteral", "NamespaceSegment",
}
var lexerRuleNames = []string{
@@ -294,9 +301,9 @@ var lexerRuleNames = []string{
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
"Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Param", "Identifier",
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
"NamespaceSegment", "HexDigit", "DecimalIntegerLiteral", "ExponentPart",
"Letter", "Symbols", "Digit", "DQSring", "SQString", "NamespaceSeparator",
"StringLiteral", "IntegerLiteral", "FloatLiteral", "NamespaceSegment",
"HexDigit", "DecimalIntegerLiteral", "ExponentPart", "Letter", "Symbols",
"Digit", "DQSring", "SQString", "BacktickString", "TickString", "NamespaceSeparator",
}
type FqlLexer struct {
@@ -334,67 +341,66 @@ func NewFqlLexer(input antlr.CharStream) *FqlLexer {
// FqlLexer tokens.
const (
FqlLexerMultiLineComment = 1
FqlLexerSingleLineComment = 2
FqlLexerWhiteSpaces = 3
FqlLexerLineTerminator = 4
FqlLexerColon = 5
FqlLexerSemiColon = 6
FqlLexerDot = 7
FqlLexerComma = 8
FqlLexerOpenBracket = 9
FqlLexerCloseBracket = 10
FqlLexerOpenParen = 11
FqlLexerCloseParen = 12
FqlLexerOpenBrace = 13
FqlLexerCloseBrace = 14
FqlLexerGt = 15
FqlLexerLt = 16
FqlLexerEq = 17
FqlLexerGte = 18
FqlLexerLte = 19
FqlLexerNeq = 20
FqlLexerMulti = 21
FqlLexerDiv = 22
FqlLexerMod = 23
FqlLexerPlus = 24
FqlLexerMinus = 25
FqlLexerMinusMinus = 26
FqlLexerPlusPlus = 27
FqlLexerAnd = 28
FqlLexerOr = 29
FqlLexerRange = 30
FqlLexerAssign = 31
FqlLexerQuestionMark = 32
FqlLexerRegexNotMatch = 33
FqlLexerRegexMatch = 34
FqlLexerFor = 35
FqlLexerReturn = 36
FqlLexerDistinct = 37
FqlLexerFilter = 38
FqlLexerSort = 39
FqlLexerLimit = 40
FqlLexerLet = 41
FqlLexerCollect = 42
FqlLexerSortDirection = 43
FqlLexerNone = 44
FqlLexerNull = 45
FqlLexerBooleanLiteral = 46
FqlLexerInto = 47
FqlLexerKeep = 48
FqlLexerWith = 49
FqlLexerCount = 50
FqlLexerAll = 51
FqlLexerAny = 52
FqlLexerAggregate = 53
FqlLexerLike = 54
FqlLexerNot = 55
FqlLexerIn = 56
FqlLexerParam = 57
FqlLexerIdentifier = 58
FqlLexerStringLiteral = 59
FqlLexerTemplateStringLiteral = 60
FqlLexerIntegerLiteral = 61
FqlLexerFloatLiteral = 62
FqlLexerNamespaceSegment = 63
FqlLexerMultiLineComment = 1
FqlLexerSingleLineComment = 2
FqlLexerWhiteSpaces = 3
FqlLexerLineTerminator = 4
FqlLexerColon = 5
FqlLexerSemiColon = 6
FqlLexerDot = 7
FqlLexerComma = 8
FqlLexerOpenBracket = 9
FqlLexerCloseBracket = 10
FqlLexerOpenParen = 11
FqlLexerCloseParen = 12
FqlLexerOpenBrace = 13
FqlLexerCloseBrace = 14
FqlLexerGt = 15
FqlLexerLt = 16
FqlLexerEq = 17
FqlLexerGte = 18
FqlLexerLte = 19
FqlLexerNeq = 20
FqlLexerMulti = 21
FqlLexerDiv = 22
FqlLexerMod = 23
FqlLexerPlus = 24
FqlLexerMinus = 25
FqlLexerMinusMinus = 26
FqlLexerPlusPlus = 27
FqlLexerAnd = 28
FqlLexerOr = 29
FqlLexerRange = 30
FqlLexerAssign = 31
FqlLexerQuestionMark = 32
FqlLexerRegexNotMatch = 33
FqlLexerRegexMatch = 34
FqlLexerFor = 35
FqlLexerReturn = 36
FqlLexerDistinct = 37
FqlLexerFilter = 38
FqlLexerSort = 39
FqlLexerLimit = 40
FqlLexerLet = 41
FqlLexerCollect = 42
FqlLexerSortDirection = 43
FqlLexerNone = 44
FqlLexerNull = 45
FqlLexerBooleanLiteral = 46
FqlLexerInto = 47
FqlLexerKeep = 48
FqlLexerWith = 49
FqlLexerCount = 50
FqlLexerAll = 51
FqlLexerAny = 52
FqlLexerAggregate = 53
FqlLexerLike = 54
FqlLexerNot = 55
FqlLexerIn = 56
FqlLexerParam = 57
FqlLexerIdentifier = 58
FqlLexerStringLiteral = 59
FqlLexerIntegerLiteral = 60
FqlLexerFloatLiteral = 61
FqlLexerNamespaceSegment = 62
)

File diff suppressed because it is too large Load Diff

View File

@@ -250,12 +250,6 @@ func (s *BaseFqlParserListener) EnterPropertyAssignment(ctx *PropertyAssignmentC
// ExitPropertyAssignment is called when production propertyAssignment is exited.
func (s *BaseFqlParserListener) ExitPropertyAssignment(ctx *PropertyAssignmentContext) {}
// EnterMemberExpression is called when production memberExpression is entered.
func (s *BaseFqlParserListener) EnterMemberExpression(ctx *MemberExpressionContext) {}
// ExitMemberExpression is called when production memberExpression is exited.
func (s *BaseFqlParserListener) ExitMemberExpression(ctx *MemberExpressionContext) {}
// EnterShorthandPropertyName is called when production shorthandPropertyName is entered.
func (s *BaseFqlParserListener) EnterShorthandPropertyName(ctx *ShorthandPropertyNameContext) {}
@@ -292,6 +286,24 @@ func (s *BaseFqlParserListener) EnterFunctionCallExpression(ctx *FunctionCallExp
// ExitFunctionCallExpression is called when production functionCallExpression is exited.
func (s *BaseFqlParserListener) ExitFunctionCallExpression(ctx *FunctionCallExpressionContext) {}
// EnterMember is called when production member is entered.
func (s *BaseFqlParserListener) EnterMember(ctx *MemberContext) {}
// ExitMember is called when production member is exited.
func (s *BaseFqlParserListener) ExitMember(ctx *MemberContext) {}
// EnterMemberPath is called when production memberPath is entered.
func (s *BaseFqlParserListener) EnterMemberPath(ctx *MemberPathContext) {}
// ExitMemberPath is called when production memberPath is exited.
func (s *BaseFqlParserListener) ExitMemberPath(ctx *MemberPathContext) {}
// EnterMemberExpression is called when production memberExpression is entered.
func (s *BaseFqlParserListener) EnterMemberExpression(ctx *MemberExpressionContext) {}
// ExitMemberExpression is called when production memberExpression is exited.
func (s *BaseFqlParserListener) ExitMemberExpression(ctx *MemberExpressionContext) {}
// EnterArguments is called when production arguments is entered.
func (s *BaseFqlParserListener) EnterArguments(ctx *ArgumentsContext) {}

View File

@@ -159,10 +159,6 @@ func (v *BaseFqlParserVisitor) VisitPropertyAssignment(ctx *PropertyAssignmentCo
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMemberExpression(ctx *MemberExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitShorthandPropertyName(ctx *ShorthandPropertyNameContext) interface{} {
return v.VisitChildren(ctx)
}
@@ -187,6 +183,18 @@ func (v *BaseFqlParserVisitor) VisitFunctionCallExpression(ctx *FunctionCallExpr
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMember(ctx *MemberContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMemberPath(ctx *MemberPathContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMemberExpression(ctx *MemberExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitArguments(ctx *ArgumentsContext) interface{} {
return v.VisitChildren(ctx)
}

View File

@@ -121,9 +121,6 @@ type FqlParserListener interface {
// EnterPropertyAssignment is called when entering the propertyAssignment production.
EnterPropertyAssignment(c *PropertyAssignmentContext)
// EnterMemberExpression is called when entering the memberExpression production.
EnterMemberExpression(c *MemberExpressionContext)
// EnterShorthandPropertyName is called when entering the shorthandPropertyName production.
EnterShorthandPropertyName(c *ShorthandPropertyNameContext)
@@ -142,6 +139,15 @@ type FqlParserListener interface {
// EnterFunctionCallExpression is called when entering the functionCallExpression production.
EnterFunctionCallExpression(c *FunctionCallExpressionContext)
// EnterMember is called when entering the member production.
EnterMember(c *MemberContext)
// EnterMemberPath is called when entering the memberPath production.
EnterMemberPath(c *MemberPathContext)
// EnterMemberExpression is called when entering the memberExpression production.
EnterMemberExpression(c *MemberExpressionContext)
// EnterArguments is called when entering the arguments production.
EnterArguments(c *ArgumentsContext)
@@ -292,9 +298,6 @@ type FqlParserListener interface {
// ExitPropertyAssignment is called when exiting the propertyAssignment production.
ExitPropertyAssignment(c *PropertyAssignmentContext)
// ExitMemberExpression is called when exiting the memberExpression production.
ExitMemberExpression(c *MemberExpressionContext)
// ExitShorthandPropertyName is called when exiting the shorthandPropertyName production.
ExitShorthandPropertyName(c *ShorthandPropertyNameContext)
@@ -313,6 +316,15 @@ type FqlParserListener interface {
// ExitFunctionCallExpression is called when exiting the functionCallExpression production.
ExitFunctionCallExpression(c *FunctionCallExpressionContext)
// ExitMember is called when exiting the member production.
ExitMember(c *MemberContext)
// ExitMemberPath is called when exiting the memberPath production.
ExitMemberPath(c *MemberPathContext)
// ExitMemberExpression is called when exiting the memberExpression production.
ExitMemberExpression(c *MemberExpressionContext)
// ExitArguments is called when exiting the arguments production.
ExitArguments(c *ArgumentsContext)

View File

@@ -121,9 +121,6 @@ type FqlParserVisitor interface {
// Visit a parse tree produced by FqlParser#propertyAssignment.
VisitPropertyAssignment(ctx *PropertyAssignmentContext) interface{}
// Visit a parse tree produced by FqlParser#memberExpression.
VisitMemberExpression(ctx *MemberExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#shorthandPropertyName.
VisitShorthandPropertyName(ctx *ShorthandPropertyNameContext) interface{}
@@ -142,6 +139,15 @@ type FqlParserVisitor interface {
// Visit a parse tree produced by FqlParser#functionCallExpression.
VisitFunctionCallExpression(ctx *FunctionCallExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#member.
VisitMember(ctx *MemberContext) interface{}
// Visit a parse tree produced by FqlParser#memberPath.
VisitMemberPath(ctx *MemberPathContext) interface{}
// Visit a parse tree produced by FqlParser#memberExpression.
VisitMemberExpression(ctx *MemberExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#arguments.
VisitArguments(ctx *ArgumentsContext) interface{}

View File

@@ -36,6 +36,14 @@ func NumberBoundaries(input float64) (max float64, min float64) {
return
}
func NumberUpperBoundary(input float64) float64 {
return input * 2
}
func NumberLowerBoundary(input float64) float64 {
return input / 2
}
func Random(max float64, min float64) float64 {
r := rand.Float64()
i := r * (max - min + 1)

View File

@@ -8,25 +8,25 @@ import (
)
type MemberExpression struct {
src core.SourceMap
variableName string
path []core.Expression
src core.SourceMap
source core.Expression
path []core.Expression
}
func NewMemberExpression(src core.SourceMap, variableName string, path []core.Expression) (*MemberExpression, error) {
if variableName == "" {
return nil, core.Error(core.ErrMissedArgument, "variable name")
func NewMemberExpression(src core.SourceMap, source core.Expression, path []core.Expression) (*MemberExpression, error) {
if source == nil {
return nil, core.Error(core.ErrMissedArgument, "source")
}
if len(path) == 0 {
return nil, core.Error(core.ErrMissedArgument, "path expressions")
}
return &MemberExpression{src, variableName, path}, nil
return &MemberExpression{src, source, path}, nil
}
func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
val, err := scope.GetVariable(e.variableName)
val, err := e.source.Exec(ctx, scope)
if err != nil {
return values.None, core.SourceError(
@@ -35,22 +35,24 @@ func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Va
)
}
strPath := make([]core.Value, len(e.path))
out := val
path := make([]core.Value, 1)
for idx, exp := range e.path {
for _, exp := range e.path {
segment, err := exp.Exec(ctx, scope)
if err != nil {
return values.None, err
}
strPath[idx] = segment
}
path[0] = segment
c, err := values.GetIn(ctx, out, path)
out, err := values.GetIn(ctx, val, strPath)
if err != nil {
return values.None, core.SourceError(e.src, err)
}
if err != nil {
return values.None, core.SourceError(e.src, err)
out = c
}
return out, nil

View File

@@ -329,7 +329,7 @@ func ToInt(input core.Value) Int {
}
}
func ToArray(ctx context.Context, input core.Value) core.Value {
func ToArray(ctx context.Context, input core.Value) *Array {
switch value := input.(type) {
case Boolean,
Int,
@@ -339,7 +339,7 @@ func ToArray(ctx context.Context, input core.Value) core.Value {
return NewArrayWith(value)
case *Array:
return value.Copy()
return value.Copy().(*Array)
case *Object:
arr := NewArray(int(value.Length()))
@@ -354,7 +354,7 @@ func ToArray(ctx context.Context, input core.Value) core.Value {
iterator, err := value.Iterate(ctx)
if err != nil {
return None
return NewArray(0)
}
arr := NewArray(10)
@@ -363,7 +363,7 @@ func ToArray(ctx context.Context, input core.Value) core.Value {
val, _, err := iterator.Next(ctx)
if err != nil {
return None
return NewArray(0)
}
if val == None {

View File

@@ -390,9 +390,7 @@ func TestHelpers(t *testing.T) {
}
input := values.NewArrayWith(vals...)
output := values.ToArray(context.Background(), input)
arr := output.(*values.Array)
arr := values.ToArray(context.Background(), input)
So(input == arr, ShouldBeFalse)
So(arr.Length() == input.Length(), ShouldBeTrue)
@@ -414,9 +412,7 @@ func TestHelpers(t *testing.T) {
values.NewObjectProperty("qaz", values.NewObject()),
)
output := values.ToArray(context.Background(), input)
arr := output.(*values.Array).Sort()
arr := values.ToArray(context.Background(), input).Sort()
So(arr.String(), ShouldEqual, "[1,\"bar\",{}]")
So(arr.Get(values.NewInt(2)) == input.MustGet("qaz"), ShouldBeTrue)

View File

@@ -40,3 +40,7 @@ func (t *none) Hash() uint64 {
func (t *none) Copy() core.Value {
return None
}
func (t *none) Clone() core.Cloneable {
return None
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// AttributeGet gets single or more attribute(s) of a given element.
// ATTR_GET 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.
@@ -27,7 +27,11 @@ func AttributeGet(ctx context.Context, args ...core.Value) (core.Value, error) {
names := args[1:]
result := values.NewObject()
attrs := el.GetAttributes(ctx)
attrs, err := el.GetAttributes(ctx)
if err != nil {
return values.None, err
}
for _, n := range names {
name := values.NewString(n.String())

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeRemove removes single or more attribute(s) of a given element.
// ATTR_REMOVE 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) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeSet sets or updates a single or more attribute(s) of a given element.
// ATTR_SET 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.

32
pkg/stdlib/html/blur.go Normal file
View File

@@ -0,0 +1,32 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
// BLUR Calls blur on the element.
// @param target (HTMLPage | HTMLDocument | HTMLElement) - Target node.
// @param selector (String, optional) - Optional CSS selector.
func Blur(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.None, err
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
if len(args) == 1 {
return values.None, el.Blur(ctx)
}
return values.None, el.BlurBySelector(ctx, values.ToString(args[1]))
}

33
pkg/stdlib/html/clear.go Normal file
View File

@@ -0,0 +1,33 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// INPUT_CLEAR clears a value from an underlying input element.
// @param source (HTMLPage | HTMLDocument | HTMLElement) - Event target.
// @param selector (String, options) - Selector.
func InputClear(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.None, err
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
// CLEAR(el)
if len(args) == 1 {
return values.None, el.Clear(ctx)
}
return values.None, el.ClearBySelector(ctx, values.ToString(args[1]))
}

View File

@@ -6,13 +6,15 @@ import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Click dispatches click event on a given element
// CLICK dispatches click event on a given element
// @param source (Open | GetElement) - Event source.
// @param selector (String, optional) - Optional selector.
// @param selectorOrCount (String | Int, optional) - Optional selector or count of clicks.
// @param count (Int, optional) - Optional count of clicks.
func Click(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
err := core.ValidateArgs(args, 1, 3)
if err != nil {
return values.False, err
@@ -26,7 +28,44 @@ func Click(ctx context.Context, args ...core.Value) (core.Value, error) {
// CLICK(elOrDoc)
if len(args) == 1 {
return values.True, el.Click(ctx)
return values.True, el.Click(ctx, 1)
}
if len(args) == 2 {
err := core.ValidateType(args[1], types.String, types.Int)
if err != nil {
return values.False, err
}
if args[1].Type() == types.String {
selector := values.ToString(args[1])
exists, err := el.ExistsBySelector(ctx, selector)
if err != nil {
return values.False, err
}
if !exists {
return exists, nil
}
return exists, el.ClickBySelector(ctx, selector, 1)
}
return values.True, el.Click(ctx, values.ToInt(args[1]))
}
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.False, err
}
err = core.ValidateType(args[2], types.Int)
if err != nil {
return values.False, err
}
// CLICK(doc, selector)
@@ -41,5 +80,7 @@ func Click(ctx context.Context, args ...core.Value) (core.Value, error) {
return exists, nil
}
return exists, el.ClickBySelector(ctx, selector)
count := values.ToInt(args[2])
return exists, el.ClickBySelector(ctx, selector, count)
}

View File

@@ -6,14 +6,16 @@ import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ClickAll dispatches click event on all matched element
// CLICK_ALL dispatches click event on all matched element
// @param source (Open) - Open.
// @param selector (String) - Selector.
// @param count (Int, optional) - Optional count of clicks.
// @returns (Boolean) - Returns true if matched at least one element.
func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.False, err
@@ -22,7 +24,7 @@ func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) {
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
return values.False, err
}
selector := values.ToString(args[1])
@@ -37,5 +39,17 @@ func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.False, nil
}
return values.True, el.ClickBySelectorAll(ctx, selector)
count := values.NewInt(1)
if len(args) == 3 {
err := core.ValidateType(args[2], types.Int)
if err != nil {
return values.False, err
}
count = values.ToInt(args[2])
}
return values.True, el.ClickBySelectorAll(ctx, selector, count)
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// CookieSet gets a cookie from a given page by name.
// COOKIE_DEL gets a cookie from a given page by name.
// @param page (HTMLPage) - Target page.
// @param cookie (...HTTPCookie|String) - Cookie or cookie name to delete.
func CookieDel(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// CookieSet gets a cookie from a given page by name.
// COOKIE_GET gets a cookie from a given page by name.
// @param page (HTMLPage) - Target page.
// @param name (String) - Cookie or cookie name to delete.
func CookieGet(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// CookieSet sets cookies to a given page
// COOKIE_SET sets cookies to a given page
// @param page (HTMLPage) - Target page.
// @param cookie... (HTTPCookie) - Target cookies.
func CookieSet(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@@ -18,7 +18,7 @@ type PageLoadParams struct {
Timeout time.Duration
}
// Open opens an HTML page by a given url.
// DOCUMENT opens an HTML page by a given url.
// By default, loads a page by http call - resulted page does not support any interactions.
// @param params (Object) - Optional, An object containing the following properties :
// driver (String) - Optional, driver name.
@@ -26,7 +26,7 @@ type PageLoadParams struct {
// userAgent (String) - Optional, user agent.
// keepCookies (Boolean) - Optional, boolean value indicating whether to use cookies from previous sessions.
// i.e. not to open a page in the Incognito mode.
// cookies (HTTPCookie) - Optional, set of HTTP cookies.
// cookies (HTTPCookies) - Optional, set of HTTP cookies.
// headers (HTTPHeaders) - Optional, HTTP headers.
// viewport (Viewport) - Optional, viewport params.
// @returns (HTMLPage) - Returns loaded HTML page.
@@ -76,7 +76,7 @@ func newDefaultDocLoadParams(url values.String) PageLoadParams {
Params: drivers.Params{
URL: url.String(),
},
Timeout: time.Second * 30,
Timeout: drivers.DefaultPageLoadTimeout * time.Millisecond,
}
}
@@ -134,17 +134,30 @@ func newPageLoadParams(url values.String, arg core.Value) (PageLoadParams, error
cookies, exists := obj.Get(values.NewString("cookies"))
if exists {
if err := core.ValidateType(cookies, types.Array); err != nil {
if err := core.ValidateType(cookies, types.Array, types.Object); err != nil {
return res, err
}
cookies, err := parseCookies(cookies.(*values.Array))
switch c := cookies.(type) {
case *values.Array:
cookies, err := parseCookieArray(c)
if err != nil {
return res, err
if err != nil {
return res, err
}
res.Cookies = cookies
case *values.Object:
cookies, err := parseCookieObject(c)
if err != nil {
return res, err
}
res.Cookies = cookies
default:
res.Cookies = make(drivers.HTTPCookies)
}
res.Cookies = cookies
}
headers, exists := obj.Get(values.NewString("headers"))
@@ -183,11 +196,11 @@ func newPageLoadParams(url values.String, arg core.Value) (PageLoadParams, error
return res, nil
}
func parseCookies(arr *values.Array) ([]drivers.HTTPCookie, error) {
func parseCookieObject(obj *values.Object) (drivers.HTTPCookies, error) {
var err error
res := make([]drivers.HTTPCookie, 0, arr.Length())
res := make(drivers.HTTPCookies)
arr.ForEach(func(value core.Value, idx int) bool {
obj.ForEach(func(value core.Value, _ string) bool {
cookie, e := parseCookie(value)
if e != nil {
@@ -196,7 +209,28 @@ func parseCookies(arr *values.Array) ([]drivers.HTTPCookie, error) {
return false
}
res = append(res, cookie)
res[cookie.Name] = cookie
return true
})
return res, err
}
func parseCookieArray(arr *values.Array) (drivers.HTTPCookies, error) {
var err error
res := make(drivers.HTTPCookies)
arr.ForEach(func(value core.Value, _ int) bool {
cookie, e := parseCookie(value)
if e != nil {
err = e
return false
}
res[cookie.Name] = cookie
return true
})

View File

@@ -10,7 +10,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Download a resource from the given GetURL.
// DOWNLOAD downloads a resource from the given GetURL.
// @param GetURL (String) - GetURL to download.
// @returns data (Binary) - Returns a base64 encoded string in binary format.
func Download(_ context.Context, args ...core.Value) (core.Value, error) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// GetElement finds an element by a given CSS selector.
// ELEMENT finds an element by a given CSS selector.
// Returns NONE if element not found.
// @param docOrEl (HTMLDocument|HTMLElement) - Parent document or element.
// @param selector (String) - CSS selector.
@@ -21,7 +21,7 @@ func Element(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
return el.QuerySelector(ctx, selector), nil
return el.QuerySelector(ctx, selector)
}
func queryArgs(args []core.Value) (drivers.HTMLElement, values.String, error) {

View File

@@ -7,7 +7,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// ElementExists returns a boolean value indicating whether there is an element matched by selector.
// ELEMENT_EXISTS returns a boolean value indicating whether there is an element matched by selector.
// @param docOrEl (HTMLDocument|HTMLNode) - Parent document or element.
// @param selector (String) - CSS selector.
// @returns (Boolean) - A boolean value indicating whether there is an element matched by selector.

View File

@@ -7,7 +7,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Elements finds HTML elements by a given CSS selector.
// ELEMENTS finds HTML elements by a given CSS selector.
// Returns an empty array if element not found.
// @param docOrEl (HTMLDocument|HTMLNode) - Parent document or element.
// @param selector (String) - CSS selector.
@@ -19,5 +19,5 @@ func Elements(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
return el.QuerySelectorAll(ctx, selector), nil
return el.QuerySelectorAll(ctx, selector)
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// ElementsCount returns a number of found HTML elements by a given CSS selector.
// ELEMENTS_COUNT returns a number of found HTML elements by a given CSS selector.
// Returns an empty array if element not found.
// @param docOrEl (HTMLDocument|HTMLNode) - Parent document or element.
// @param selector (String) - CSS selector.
@@ -19,5 +19,5 @@ func ElementsCount(ctx context.Context, args ...core.Value) (core.Value, error)
return values.None, err
}
return el.CountBySelector(ctx, selector), nil
return el.CountBySelector(ctx, selector)
}

View File

@@ -2,14 +2,15 @@ package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// FOCUS Calls focus on the element.
// FOCUS Sets focus on the element.
// @param target (HTMLPage | HTMLDocument | HTMLElement) - Target node.
// @param selector (String, optional) - Optional CSS selector. Required when target is HTMLPage or HTMLDocument.
// @param selector (String, optional) - Optional CSS selector.
func Focus(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
@@ -17,22 +18,15 @@ func Focus(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
// Document with selector
if len(args) == 2 {
doc, err := drivers.ToDocument(args[0])
if err != nil {
return values.None, err
}
return values.None, doc.FocusBySelector(ctx, values.ToString(args[1]))
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
return values.None, el.Focus(ctx)
if len(args) == 1 {
return values.None, el.Focus(ctx)
}
return values.None, el.FocusBySelector(ctx, values.ToString(args[1]))
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// GetInnerHTML Returns inner HTML string of a given or matched by CSS selector element
// INNER_HTML returns inner HTML string of a given or matched by CSS selector element
// @param doc (Open|GetElement) - Parent document or element.
// @param selector (String, optional) - String of CSS selector.
// @returns (String) - Inner HTML string if an element found, otherwise empty string.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// GetInnerHTMLAll returns an array of inner HTML strings of matched elements.
// INNER_HTML_ALL returns an array of inner HTML strings of matched elements.
// @param doc (HTMLDocument|HTMLElement) - Parent document or element.
// @param selector (String) - String of CSS selector.
// @returns (String) - An array of inner HTML strings if any element found, otherwise empty array.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// GetInnerText returns inner text string of a given or matched by CSS selector element
// INNER_TEXT returns inner text string of a given or matched by CSS selector element
// @param doc (HTMLDocument|HTMLElement) - Parent document or element.
// @param selector (String, optional) - String of CSS selector.
// @returns (String) - Inner text if an element found, otherwise empty string.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// GetInnerTextAll returns an array of inner text of matched elements.
// INNER_TEXT_ALL returns an array of inner text of matched elements.
// @param doc (HTMLDocument|HTMLElement) - Parent document or element.
// @param selector (String) - String of CSS selector.
// @returns (String) - An array of inner text if any element found, otherwise empty array.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Hover fetches an element with selector, scrolls it into view if needed, and then uses page.mouse to hover over the center of the element.
// HOVER fetches an element with selector, scrolls it into view if needed, and then uses page.mouse to hover over the center of the element.
// If there's no element matching selector, the method returns an error.
// @param docOrEl (HTMLDocument|HTMLElement) - Target document or element.
// @param selector (String, options) - If document is passed, this param must represent an element selector.
@@ -20,59 +20,21 @@ func Hover(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
// page or document or element
err = core.ValidateType(args[0], drivers.HTMLPageType, drivers.HTMLDocumentType, drivers.HTMLElementType)
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
selector := values.EmptyString
if len(args) > 1 {
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.None, err
}
selector = args[1].(values.String)
}
switch n := args[0].(type) {
case drivers.HTMLPage:
if selector == values.EmptyString {
return values.None, core.Error(core.ErrMissedArgument, "selector")
}
return values.None, n.GetMainFrame().MoveMouseBySelector(ctx, selector)
case drivers.HTMLDocument:
if selector == values.EmptyString {
return values.None, core.Error(core.ErrMissedArgument, "selector")
}
return values.None, n.MoveMouseBySelector(ctx, selector)
case drivers.HTMLElement:
if selector == values.EmptyString {
return values.None, n.Hover(ctx)
}
found := n.QuerySelector(ctx, selector)
if found == values.None {
return values.None, core.Errorf(core.ErrNotFound, "element by selector %s", selector)
}
el, ok := found.(drivers.HTMLElement)
if !ok {
return values.None, core.Errorf(core.ErrNotFound, "element by selector %s", selector)
}
defer el.Close()
if len(args) == 1 {
return values.None, el.Hover(ctx)
default:
return values.None, core.TypeError(n.Type(), drivers.HTMLDocumentType, drivers.HTMLElementType)
}
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.None, err
}
return values.None, el.HoverBySelector(ctx, values.ToString(args[1]))
}

View File

@@ -2,101 +2,83 @@ package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Input types a value to an underlying input element.
// @param source (Open | GetElement) - Event target.
// INPUT types a value to an underlying input element.
// @param source (HTMLPage | HTMLDocument | HTMLElement) - Event target.
// @param valueOrSelector (String) - Selector or a value.
// @param value (String) - Target value.
// @param delay (Int, optional) - Waits delay milliseconds between keystrokes
// @param delay (Int, optional) - Target value.
// @returns (Boolean) - Returns true if an element was found.
func Input(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.None, err
return values.False, err
}
arg1 := args[0]
err = core.ValidateType(arg1, drivers.HTMLPageType, drivers.HTMLDocumentType, drivers.HTMLElementType)
el, err := drivers.ToElement(args[0])
if err != nil {
return values.False, err
}
if arg1.Type() == drivers.HTMLPageType || arg1.Type() == drivers.HTMLDocumentType {
doc, err := drivers.ToDocument(arg1)
delay := values.NewInt(drivers.DefaultKeyboardDelay)
if err != nil {
return values.False, err
}
// INPUT(el, value)
if len(args) == 2 {
return values.True, el.Input(ctx, args[1], delay)
}
// selector
arg2 := args[1]
err = core.ValidateType(arg2, types.String)
var selector values.String
var value core.Value
if err != nil {
return values.False, err
}
// INPUT(el, valueOrSelector, valueOrOpts)
if len(args) == 3 {
switch v := args[2].(type) {
// INPUT(el, value, delay)
case values.Int, values.Float:
value = args[1]
delay = values.ToInt(v)
selector := values.ToString(arg2)
delay := values.Int(0)
if len(args) == 4 {
arg4 := args[3]
err = core.ValidateType(arg4, types.Int)
if err != nil {
return values.True, el.Input(ctx, value, delay)
default:
// INPUT(el, selector, value)
if err := core.ValidateType(args[1], types.String); err != nil {
return values.False, err
}
delay = values.ToInt(arg4)
selector = values.ToString(args[1])
value = args[2]
}
exists, err := doc.ExistsBySelector(ctx, selector)
if err != nil {
} else {
// INPUT(el, selector, value, delay)
if err := core.ValidateType(args[1], types.String); err != nil {
return values.False, err
}
if !exists {
return values.False, nil
}
return values.True, doc.InputBySelector(ctx, selector, args[2], delay)
}
el, err := drivers.ToElement(arg1)
if err != nil {
return values.None, err
}
delay := values.Int(0)
if len(args) == 3 {
arg3 := args[2]
err = core.ValidateType(arg3, types.Int)
if err != nil {
if err := core.ValidateType(args[3], types.Int); err != nil {
return values.False, err
}
delay = arg3.(values.Int)
selector = values.ToString(args[1])
value = args[2]
delay = values.ToInt(args[3])
}
err = el.Input(ctx, args[1], delay)
exists, err := el.ExistsBySelector(ctx, selector)
if err != nil {
return values.False, err
}
return values.True, nil
if !exists {
return values.False, nil
}
return values.True, el.InputBySelector(ctx, selector, value, delay)
}

View File

@@ -10,13 +10,12 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
const defaultTimeout = 5000
func RegisterLib(ns core.Namespace) error {
return ns.RegisterFunctions(core.Functions{
"ATTR_GET": AttributeGet,
"ATTR_REMOVE": AttributeRemove,
"ATTR_SET": AttributeSet,
"BLUR": Blur,
"COOKIE_DEL": CookieDel,
"COOKIE_GET": CookieGet,
"COOKIE_SET": CookieSet,
@@ -37,6 +36,7 @@ func RegisterLib(ns core.Namespace) error {
"INNER_TEXT_SET": SetInnerText,
"INNER_TEXT_ALL": GetInnerTextAll,
"INPUT": Input,
"INPUT_CLEAR": InputClear,
"MOUSE": MouseMoveXY,
"NAVIGATE": Navigate,
"NAVIGATE_BACK": NavigateBack,
@@ -73,6 +73,7 @@ func RegisterLib(ns core.Namespace) error {
func OpenOrCastPage(ctx context.Context, value core.Value) (drivers.HTMLPage, bool, error) {
err := core.ValidateType(value, drivers.HTMLPageType, types.String)
if err != nil {
return nil, false, err
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// MouseMoveXY moves mouse by given coordinates.
// MOUSE moves mouse by given coordinates.
// @param doc (HTMLDocument) - HTML document.
// @param x (Int|Float) - X coordinate.
// @param y (Int|Float) - Y coordinate.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Navigate navigates a given page to a new resource.
// NAVIGATE navigates a given page to a new resource.
// The operation blocks the execution until the page gets loaded.
// Which means there is no need in WAIT_NAVIGATION function.
// @param page (HTMLPage) - Target page.
@@ -34,7 +34,7 @@ func Navigate(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) > 2 {
err = core.ValidateType(args[2], types.Int)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// NavigateBack navigates a given page back within its navigation history.
// NAVIGATE_BACK navigates a given page back within its navigation history.
// The operation blocks the execution until the page gets loaded.
// If the history is empty, the function returns FALSE.
// @param page (HTMLPage) - Target page.
@@ -30,7 +30,7 @@ func NavigateBack(ctx context.Context, args ...core.Value) (core.Value, error) {
}
skip := values.NewInt(1)
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) > 1 {
err = core.ValidateType(args[1], types.Int)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// NavigateForward navigates a given page forward within its navigation history.
// NAVIGATE_FORWARD navigates a given page forward within its navigation history.
// The operation blocks the execution until the page gets loaded.
// If the history is empty, the function returns FALSE.
// @param page (HTMLPage) - Target page.
@@ -30,7 +30,7 @@ func NavigateForward(ctx context.Context, args ...core.Value) (core.Value, error
}
skip := values.NewInt(1)
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) > 1 {
err = core.ValidateType(args[1], types.Int)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Pagination creates an iterator that goes through pages using CSS selector.
// PAGINATION creates an iterator that goes through pages using CSS selector.
// The iterator starts from the current page i.e. it does not change the page on 1st iteration.
// That allows you to keep scraping logic inside FOR loop.
// @param doc (Open) - Target document.
@@ -102,7 +102,7 @@ func (i *PagingIterator) Next(ctx context.Context) (core.Value, core.Value, erro
return values.None, values.None, core.ErrNoMoreData
}
err = i.document.ClickBySelector(ctx, i.selector)
err = i.document.GetElement().ClickBySelector(ctx, i.selector, 1)
if err != nil {
return values.None, values.None, err

View File

@@ -21,7 +21,7 @@ func ValidatePageRanges(pageRanges string) (bool, error) {
return match, nil
}
// PDF print a PDF of the current page.
// PDF prints a PDF of the current page.
// @param target (HTMLPage|String) - Target page or url.
// @param params (Object) - Optional, An object containing the following properties :
// Landscape (Bool) - Paper orientation. Defaults to false.

View File

@@ -10,7 +10,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Screenshot takes a screenshot of a given page.
// SCREENSHOT takes a screenshot of a given page.
// @param target (HTMLPage|String) - Target page or url.
// @param params (Object) - Optional, An object containing the following properties :
// x (Float|Int) - Optional, X position of the viewport.

View File

@@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// ScrollTop scrolls the document's window to its bottom.
// SCROLL_BOTTOM scrolls the document's window to its bottom.
// @param doc (HTMLDocument) - Target document.
func ScrollBottom(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ScrollInto scrolls an element on.
// SCROLL_ELEMENT scrolls an element on.
// @param docOrEl (HTMLDocument|HTMLElement) - Target document or element.
// @param selector (String, options) - If document is passed, this param must represent an element selector.
func ScrollInto(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// ScrollTop scrolls the document's window to its top.
// SCROLL_TOP scrolls the document's window to its top.
// @param doc (HTMLDocument) - Target document.
func ScrollTop(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ScrollXY scrolls by given coordinates.
// SCROLL scrolls by given coordinates.
// @param doc (HTMLDocument) - HTML document.
// @param x (Int|Float) - X coordinate.
// @param y (Int|Float) - Y coordinate.

View File

@@ -6,10 +6,9 @@ import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Select selects a value from an underlying select element.
// SELECT selects a value from an underlying select element.
// @param source (Open | GetElement) - Event target.
// @param valueOrSelector (String | Array<String>) - Selector or a an array of strings as a value.
// @param value (Array<String) - Target value. Optional.
@@ -21,46 +20,20 @@ func Select(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
arg1 := args[0]
err = core.ValidateType(arg1, drivers.HTMLPageType, drivers.HTMLDocumentType, drivers.HTMLElementType)
el, err := drivers.ToElement(args[0])
if err != nil {
return values.False, err
return values.None, err
}
if arg1.Type() == drivers.HTMLPageType || arg1.Type() == drivers.HTMLDocumentType {
doc, err := drivers.ToDocument(arg1)
if len(args) == 2 {
arr := values.ToArray(ctx, args[1])
if err != nil {
return values.None, err
}
// selector
arg2 := args[1]
err = core.ValidateType(arg2, types.String)
if err != nil {
return values.False, err
}
arg3 := args[2]
err = core.ValidateType(arg3, types.Array)
if err != nil {
return values.False, err
}
return doc.SelectBySelector(ctx, arg2.(values.String), arg3.(*values.Array))
return el.Select(ctx, arr)
}
el := arg1.(drivers.HTMLElement)
arg2 := args[1]
selector := values.ToString(args[1])
arr := values.ToArray(ctx, args[2])
err = core.ValidateType(arg2, types.Array)
if err != nil {
return values.False, err
}
return el.Select(ctx, arg2.(*values.Array))
return el.SelectBySelector(ctx, selector, arr)
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// SetInnerHTML sets inner HTML string to a given or matched by CSS selector element
// INNER_HTML_SET sets inner HTML string to a given or matched by CSS selector element
// @param doc (Open|GetElement) - Parent document or element.
// @param selector (String, optional) - String of CSS selector.
// @param innerHTML (String) - String of inner HTML.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// SetInnerText sets inner text string to a given or matched by CSS selector element
// INNER_TEXT_SET sets inner text string to a given or matched by CSS selector element
// @param doc (Open|GetElement) - Parent document or element.
// @param selector (String, optional) - String of CSS selector.
// @param innerText (String) - String of inner text.

View File

@@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// StyleGet gets single or more style attribute value(s) of a given element.
// STYLE_GET 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.

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// StyleRemove removes single or more style attribute value(s) of a given element.
// STYLE_REMOVE 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) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// StyleSet sets or updates a single or more style attribute value of a given element.
// STYLE_SET 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.

View File

@@ -9,10 +9,22 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WAIT_ATTR waits until a target attribute's value appears
// @param node (HTMLPage | HTMLDocument | HTMLElement) - Parent document.
// @param attrNameOrSelector (String) - String of an attr name or CSS selector.
// @param attrValueOrAttrName (String | Any) - Attr value or name.
// @param attrValueOrTimeout (Any | Int, optional) - Attr value or an optional timeout.
// @param timeout (Int, optional) - Optional timeout.
func WaitAttribute(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitAttributeWhen(ctx, args, drivers.WaitEventPresence)
}
// WAIT_NO_ATTR waits until a target attribute's value disappears
// @param node (HTMLPage | HTMLDocument | HTMLElement) - Parent document.
// @param attrNameOrSelector (String) - String of an attr name or CSS selector.
// @param attrValueOrAttrName (String | Any) - Attr value or name.
// @param attrValueOrTimeout (Any | Int, optional) - Attr value or an optional timeout.
// @param timeout (Int, optional) - Optional timeout.
func WaitNoAttribute(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitAttributeWhen(ctx, args, drivers.WaitEventAbsence)
}
@@ -39,7 +51,7 @@ func waitAttributeWhen(ctx context.Context, args []core.Value, when drivers.Wait
return values.None, err
}
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
// if a document is passed
// WAIT_ATTR(doc, selector, attrName, attrValue, timeout)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WaitClassAll waits for a class to appear on all matched elements.
// WAIT_ATTR_ALL waits for an attribute to appear on all matched elements with a given value.
// Stops the execution until the navigation ends or operation times out.
// @param doc (HTMLDocument) - Parent document.
// @param selector (String) - String of CSS selector.
@@ -19,7 +19,7 @@ func WaitAttributeAll(ctx context.Context, args ...core.Value) (core.Value, erro
return waitAttributeAllWhen(ctx, args, drivers.WaitEventPresence)
}
// WaitClassAll waits for a class to disappear on all matched elements.
// WAIT_NO_ATTR_ALL waits for an attribute to disappear on all matched elements by a given value.
// Stops the execution until the navigation ends or operation times out.
// @param doc (HTMLDocument) - Parent document.
// @param selector (String) - String of CSS selector.
@@ -59,7 +59,7 @@ func waitAttributeAllWhen(ctx context.Context, args []core.Value, when drivers.W
selector := args[1].(values.String)
name := args[2].(values.String)
value := args[3]
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) == 5 {
err = core.ValidateType(args[4], types.Int)

View File

@@ -9,9 +9,9 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WaitClass waits for a class to appear on a given element.
// WAIT_CLASS waits for a class to appear on a given element.
// Stops the execution until the navigation ends or operation times out.
// @param docOrEl (HTMLDocument|HTMLElement) - Target document or element.
// @param node (HTMLPage | HTMLDocument | HTMLElement) - Target node.
// @param selectorOrClass (String) - If document is passed, this param must represent an element selector.
// Otherwise target class.
// @param classOrTimeout (String|Int, optional) - If document is passed, this param must represent target class name.
@@ -22,9 +22,9 @@ func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitClassWhen(ctx, args, drivers.WaitEventPresence)
}
// WaitClass waits for a class to disappear on a given element.
// WAIT_NO_CLASS waits for a class to disappear on a given element.
// Stops the execution until the navigation ends or operation times out.
// @param docOrEl (HTMLDocument|HTMLElement) - Target document or element.
// @param node (HTMLPage | HTMLDocument | HTMLElement) - Target node.
// @param selectorOrClass (String) - If document is passed, this param must represent an element selector.
// Otherwise target class.
// @param classOrTimeout (String|Int, optional) - If document is passed, this param must represent target class name.
@@ -57,7 +57,7 @@ func waitClassWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
return values.None, err
}
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
// if a document is passed
if arg1.Type() == drivers.HTMLPageType || arg1.Type() == drivers.HTMLDocumentType {

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WaitClassAll waits for a class to appear on all matched elements.
// WAIT_CLASS_ALL waits for a class to appear on all matched elements.
// Stops the execution until the navigation ends or operation times out.
// @param doc (HTMLDocument) - Parent document.
// @param selector (String) - String of CSS selector.
@@ -19,7 +19,7 @@ func WaitClassAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitClassAllWhen(ctx, args, drivers.WaitEventPresence)
}
// WaitClassAll waits for a class to disappear on all matched elements.
// WAIT_NO_CLASS_ALL waits for a class to disappear on all matched elements.
// Stops the execution until the navigation ends or operation times out.
// @param doc (HTMLDocument) - Parent document.
// @param selector (String) - String of CSS selector.
@@ -58,7 +58,7 @@ func waitClassAllWhen(ctx context.Context, args []core.Value, when drivers.WaitE
selector := args[1].(values.String)
class := args[2].(values.String)
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) == 4 {
err = core.ValidateType(args[3], types.Int)

View File

@@ -9,16 +9,16 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WaitElement waits for element to appear in the DOM.
// WAIT_ELEMENT waits for element to appear in the DOM.
// Stops the execution until it finds an element or operation times out.
// @param doc (HTMLDocument) - Driver HTMLDocument.
// @param n (HTMLDocument) - Driver HTMLDocument.
// @param selector (String) - Target element's selector.
// @param timeout (Int, optional) - Optional timeout. Default 5000 ms.
func WaitElement(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitElementWhen(ctx, args, drivers.WaitEventPresence)
}
// WaitNoElements waits for element to disappear in the DOM.
// WAIT_NO_ELEMENT waits for element to disappear in the DOM.
// Stops the execution until it does not find an element or operation times out.
// @param doc (HTMLDocument) - Driver HTMLDocument.
// @param selector (String) - Target element's selector.
@@ -41,7 +41,7 @@ func waitElementWhen(ctx context.Context, args []core.Value, when drivers.WaitEv
}
selector := args[1].String()
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) > 2 {
err = core.ValidateType(args[2], types.Int)

View File

@@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WaitNavigation waits for a given page to navigate to a new url.
// WAIT_NAVIGATION waits for a given page to navigate to a new url.
// Stops the execution until the navigation ends or operation times out.
// @param page (HTMLPage) - Target page.
// @param timeout (Int, optional) - Optional timeout. Default 5000 ms.
@@ -26,7 +26,7 @@ func WaitNavigation(ctx context.Context, args ...core.Value) (core.Value, error)
return values.None, err
}
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) > 1 {
err = core.ValidateType(args[1], types.Int)

View File

@@ -9,10 +9,12 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WAIT_STYLE
func WaitStyle(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitStyleWhen(ctx, args, drivers.WaitEventPresence)
}
// WAIT_NO_STYLE
func WaitNoStyle(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitStyleWhen(ctx, args, drivers.WaitEventAbsence)
}
@@ -39,7 +41,7 @@ func waitStyleWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
return values.None, err
}
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
// if a document is passed
// WAIT_ATTR(doc, selector, attrName, attrValue, timeout)

View File

@@ -9,10 +9,12 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WAIT_STYLE_ALL
func WaitStyleAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitStyleAllWhen(ctx, args, drivers.WaitEventPresence)
}
// WAIT_NO_STYLE_ALL
func WaitNoStyleAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitStyleAllWhen(ctx, args, drivers.WaitEventAbsence)
}
@@ -47,7 +49,7 @@ func waitStyleAllWhen(ctx context.Context, args []core.Value, when drivers.WaitE
selector := args[1].(values.String)
name := args[2].(values.String)
value := args[3]
timeout := values.NewInt(defaultTimeout)
timeout := values.NewInt(drivers.DefaultWaitTimeout)
if len(args) == 5 {
err = core.ValidateType(args[4], types.Int)

Some files were not shown because too many files have changed in this diff Show More