Skip to content

Commit

Permalink
Merge PR #88 from srebhan/string_join
Browse files Browse the repository at this point in the history
Implement string-join function
  • Loading branch information
zhengchun authored May 5, 2023
2 parents 4fa1ebb + 86ac085 commit 588960c
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 6 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Supported Features

- `a/b` : For each node matching a, add the nodes matching b to the result.

- `a//b` : For each node matching a, add the descendant nodes matching b to the result.
- `a//b` : For each node matching a, add the descendant nodes matching b to the result.

- `//b` : Returns elements in the entire document matching b.

Expand All @@ -59,7 +59,7 @@ Supported Features

- `(a/b)` : Selects all matches nodes as grouping set.

#### Node Axes
#### Node Axes

- `child::*` : The child axis selects children of the current node.

Expand All @@ -73,9 +73,9 @@ Supported Features

- `preceding-sibling::*` : Selects nodes before the current node.

- `following::*` : Selects the first matching node following in document order, excluding descendants.
- `following::*` : Selects the first matching node following in document order, excluding descendants.

- `preceding::*` : Selects the first matching node preceding in document order, excluding ancestors.
- `preceding::*` : Selects the first matching node preceding in document order, excluding ancestors.

- `parent::*` : Selects the parent if it matches. The '..' pattern from the core is equivalent to 'parent::node()'.

Expand Down Expand Up @@ -151,6 +151,7 @@ Supported Features
`round()`| ✓ |
`starts-with()`| ✓ |
`string()`| ✓ |
`string-join()`[^1]| ✓ |
`string-length()`| ✓ |
`substring()`| ✓ |
`substring-after()`| ✓ |
Expand All @@ -159,4 +160,6 @@ Supported Features
`system-property()`| ✗ |
`translate()`| ✓ |
`true()`| ✓ |
`unparsed-entity-url()` | ✗ |
`unparsed-entity-url()` | ✗ |

[^1]: XPath-2.0 expression
13 changes: 13 additions & 0 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,19 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
return nil, err
}
qyOutput = &transformFunctionQuery{Input: argQuery, Func: reverseFunc}
case "string-join":
if len(root.Args) != 2 {
return nil, fmt.Errorf("xpath: string-join(node-sets, separator) function requires node-set and argument")
}
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
arg1, err := b.processNode(root.Args[1])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: argQuery, Func: stringJoinFunc(arg1)}
default:
return nil, fmt.Errorf("not yet support this function %s()", root.FuncName)
}
Expand Down
31 changes: 31 additions & 0 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,3 +614,34 @@ func reverseFunc(q query, t iterator) func() NodeNavigator {
return node
}
}

// string-join is a XPath Node Set functions string-join(node-set, separator).
func stringJoinFunc(arg1 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var separator string
switch v := functionArgs(arg1).Evaluate(t).(type) {
case string:
separator = v
case query:
node := v.Select(t)
if node != nil {
separator = node.Value()
}
}

q = functionArgs(q)
test := predicate(q)
var parts []string
switch v := q.Evaluate(t).(type) {
case string:
return v
case query:
for node := v.Select(t); node != nil; node = v.Select(t) {
if test(node) {
parts = append(parts, node.Value())
}
}
}
return strings.Join(parts, separator)
}
}
6 changes: 5 additions & 1 deletion xpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ func TestFunction(t *testing.T) {
`translate('The quick brown fox.', 'brown', 'red')`,
"The quick red fdx.",
)
testEval(t, html, `string-join(//li/a,';')`, "Home;about;login")
testEval(t, html, `string-join('some text',';')`, "some text")
// preceding-sibling::*
testXPath3(t, html, "//li[last()]/preceding-sibling::*[2]", selectNode(html, "//li[position()=2]"))
// preceding::
Expand Down Expand Up @@ -404,7 +406,9 @@ func TestPanic(t *testing.T) {
assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,'')=0]", nil) })
assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,4)=0]", nil) })
//assertPanic(t, func() { testXPath2(t, html, "//title[substring(child::*,0) = '']", 0) }) // Here substring return boolen (false), should it?

//string-join
assertPanic(t, func() { testXPath3(t, html, "string-join()", nil) })
assertPanic(t, func() { testXPath3(t, html, "string-join(//li/a)", nil) })
}

func TestEvaluate(t *testing.T) {
Expand Down

0 comments on commit 588960c

Please sign in to comment.