diff --git a/build.go b/build.go
index a93c8eb..ba62ef7 100644
--- a/build.go
+++ b/build.go
@@ -698,7 +698,8 @@ func (b *builder) processNode(root node, flags flag, props *builderProp) (q quer
}
// build builds a specified XPath expressions expr.
-func build(expr string, namespaces map[string]string) (q query, err error) {
+// build returns the query and the parser used for parsing.
+func build(expr string, namespaces map[string]string) (q query, p *parser, err error) {
defer func() {
if e := recover(); e != nil {
switch x := e.(type) {
@@ -711,8 +712,9 @@ func build(expr string, namespaces map[string]string) (q query, err error) {
}
}
}()
- root := parse(expr, namespaces)
+ root, p := parse(expr, namespaces)
b := &builder{}
props := builderProps.None
- return b.processNode(root, flagsEnum.None, &props)
+ q, err = b.processNode(root, flagsEnum.None, &props)
+ return q, p, err
}
diff --git a/parse.go b/parse.go
index 5393125..2ca8123 100644
--- a/parse.go
+++ b/parse.go
@@ -564,12 +564,14 @@ func (p *parser) parseMethod(n node) node {
}
// Parse parsing the XPath express string expr and returns a tree node.
-func parse(expr string, namespaces map[string]string) node {
+// parse returns the root node and the parser used for parsing.
+func parse(expr string, namespaces map[string]string) (node, *parser) {
r := &scanner{text: expr}
r.nextChar()
r.nextItem()
p := &parser{r: r, namespaces: namespaces}
- return p.parseExpression(nil)
+ root := p.parseExpression(nil)
+ return root, p
}
// rootNode holds a top-level node of tree.
diff --git a/xpath.go b/xpath.go
index 04bbe8d..2544a12 100644
--- a/xpath.go
+++ b/xpath.go
@@ -5,6 +5,17 @@ import (
"fmt"
)
+// CompileOptions allows customizing the behavior of the XPath parser.
+type CompileOptions struct {
+ StrictEOF bool // If true, require full input consumption (no trailing tokens)
+ // Future strictness options can be added here
+}
+
+// StrictPreset enables all strictness options (update as new options are added)
+var StrictPreset = CompileOptions{
+ StrictEOF: true,
+}
+
// NodeType represents a type of XPath node.
type NodeType int
@@ -138,17 +149,7 @@ func (expr *Expr) String() string {
// Compile compiles an XPath expression string.
func Compile(expr string) (*Expr, error) {
- if expr == "" {
- return nil, errors.New("expr expression is nil")
- }
- qy, err := build(expr, nil)
- if err != nil {
- return nil, err
- }
- if qy == nil {
- return nil, fmt.Errorf(fmt.Sprintf("undeclared variable in XPath expression: %s", expr))
- }
- return &Expr{s: expr, q: qy}, nil
+ return CompileWithOptionsAndNS(expr, CompileOptions{}, nil)
}
// MustCompile compiles an XPath expression string and ignored error.
@@ -162,15 +163,27 @@ func MustCompile(expr string) *Expr {
// CompileWithNS compiles an XPath expression string, using given namespaces map.
func CompileWithNS(expr string, namespaces map[string]string) (*Expr, error) {
+ return CompileWithOptionsAndNS(expr, CompileOptions{}, namespaces)
+}
+
+// CompileWithOptions compiles an XPath expression string with the given options.
+func CompileWithOptions(expr string, opts CompileOptions) (*Expr, error) {
+ return CompileWithOptionsAndNS(expr, opts, nil)
+}
+
+func CompileWithOptionsAndNS(expr string, opts CompileOptions, namespaces map[string]string) (*Expr, error) {
if expr == "" {
return nil, errors.New("expr expression is nil")
}
- qy, err := build(expr, namespaces)
+ q, p, err := build(expr, namespaces)
if err != nil {
return nil, err
}
- if qy == nil {
- return nil, fmt.Errorf(fmt.Sprintf("undeclared variable in XPath expression: %s", expr))
+ if opts.StrictEOF && p != nil && p.r.typ != itemEOF {
+ return nil, fmt.Errorf("unexpected token after end of expression: %s", p.r.text[p.r.pos-p.r.currSize-1:])
+ }
+ if q == nil {
+ return nil, fmt.Errorf("undeclared variable in XPath expression: %s", expr)
}
- return &Expr{s: expr, q: qy}, nil
+ return &Expr{s: expr, q: q}, nil
}
diff --git a/xpath_test.go b/xpath_test.go
index 7379f70..1af91dd 100644
--- a/xpath_test.go
+++ b/xpath_test.go
@@ -91,26 +91,26 @@ func test_xpath_eval(t *testing.T, root *TNode, expr string, expected ...interfa
func Test_Predicates_MultiParent(t *testing.T) {
// https://github.com/antchfx/xpath/issues/75
/*
-
This is the first paragraph.
+ */