Skip to content

Commit b66f166

Browse files
committed
Implement more functions, lots of bug fixes
1 parent c041c37 commit b66f166

File tree

12 files changed

+2382
-107
lines changed

12 files changed

+2382
-107
lines changed

Readme.md

Lines changed: 102 additions & 88 deletions
Large diffs are not rendered by default.

array.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package goxpath
2+
3+
import "fmt"
4+
5+
const nsArray = "http://www.w3.org/2005/xpath-functions/array"
6+
7+
// XPathArray represents an XPath 3.1 array.
8+
type XPathArray struct {
9+
Members []Sequence
10+
}
11+
12+
// Get returns the member at the given 1-based index.
13+
func (a *XPathArray) Get(pos int) (Sequence, error) {
14+
if pos < 1 || pos > len(a.Members) {
15+
return nil, fmt.Errorf("array index %d out of bounds (size %d)", pos, len(a.Members))
16+
}
17+
return a.Members[pos-1], nil
18+
}
19+
20+
// Size returns the number of members in the array.
21+
func (a *XPathArray) Size() int {
22+
return len(a.Members)
23+
}
24+
25+
func fnArrayGet(ctx *Context, args []Sequence) (Sequence, error) {
26+
if len(args[0]) != 1 {
27+
return nil, fmt.Errorf("array:get expects a single array as first argument")
28+
}
29+
arr, ok := args[0][0].(*XPathArray)
30+
if !ok {
31+
return nil, fmt.Errorf("array:get expects an array as first argument, got %T", args[0][0])
32+
}
33+
if len(args[1]) != 1 {
34+
return nil, fmt.Errorf("array:get expects a single integer as second argument")
35+
}
36+
pos, err := NumberValue(args[1])
37+
if err != nil {
38+
return nil, err
39+
}
40+
return arr.Get(int(pos))
41+
}
42+
43+
func fnArraySize(ctx *Context, args []Sequence) (Sequence, error) {
44+
if len(args[0]) != 1 {
45+
return nil, fmt.Errorf("array:size expects a single array as first argument")
46+
}
47+
arr, ok := args[0][0].(*XPathArray)
48+
if !ok {
49+
return nil, fmt.Errorf("array:size expects an array as first argument, got %T", args[0][0])
50+
}
51+
return Sequence{arr.Size()}, nil
52+
}
53+
54+
func init() {
55+
RegisterFunction(&Function{Name: "get", Namespace: nsArray, F: fnArrayGet, MinArg: 2, MaxArg: 2})
56+
RegisterFunction(&Function{Name: "size", Namespace: nsArray, F: fnArraySize, MinArg: 1, MaxArg: 1})
57+
}

casts.go

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package goxpath
22

33
import (
44
"fmt"
5+
"strconv"
6+
"strings"
57
"time"
68
)
79

@@ -10,6 +12,136 @@ var (
1012
ErrConversion = fmt.Errorf("conversion failed")
1113
)
1214

15+
// XSDuration represents an xs:duration value with separate date and time components.
16+
type XSDuration struct {
17+
Negative bool
18+
Years int
19+
Months int
20+
Days int
21+
Hours int
22+
Minutes int
23+
Seconds float64
24+
}
25+
26+
func (d XSDuration) String() string {
27+
var sb strings.Builder
28+
if d.Negative {
29+
sb.WriteByte('-')
30+
}
31+
sb.WriteByte('P')
32+
if d.Years != 0 {
33+
fmt.Fprintf(&sb, "%dY", d.Years)
34+
}
35+
if d.Months != 0 {
36+
fmt.Fprintf(&sb, "%dM", d.Months)
37+
}
38+
if d.Days != 0 {
39+
fmt.Fprintf(&sb, "%dD", d.Days)
40+
}
41+
if d.Hours != 0 || d.Minutes != 0 || d.Seconds != 0 {
42+
sb.WriteByte('T')
43+
if d.Hours != 0 {
44+
fmt.Fprintf(&sb, "%dH", d.Hours)
45+
}
46+
if d.Minutes != 0 {
47+
fmt.Fprintf(&sb, "%dM", d.Minutes)
48+
}
49+
if d.Seconds != 0 {
50+
s := strconv.FormatFloat(d.Seconds, 'f', -1, 64)
51+
sb.WriteString(s)
52+
sb.WriteByte('S')
53+
}
54+
}
55+
if sb.Len() == 1 || (d.Negative && sb.Len() == 2) {
56+
sb.WriteString("T0S")
57+
}
58+
return sb.String()
59+
}
60+
61+
// ParseXSDuration parses an ISO 8601 duration string (e.g. "P1Y2M3DT4H5M6.5S", "-P1Y").
62+
func ParseXSDuration(s string) (XSDuration, error) {
63+
var d XSDuration
64+
orig := s
65+
if len(s) == 0 {
66+
return d, fmt.Errorf("FORG0001: empty duration string")
67+
}
68+
if s[0] == '-' {
69+
d.Negative = true
70+
s = s[1:]
71+
}
72+
if len(s) == 0 || s[0] != 'P' {
73+
return d, fmt.Errorf("FORG0001: duration must start with 'P': %q", orig)
74+
}
75+
s = s[1:]
76+
inTimePart := false
77+
for len(s) > 0 {
78+
if s[0] == 'T' {
79+
inTimePart = true
80+
s = s[1:]
81+
continue
82+
}
83+
// Read a number (possibly decimal)
84+
numEnd := 0
85+
for numEnd < len(s) && (s[numEnd] >= '0' && s[numEnd] <= '9' || s[numEnd] == '.') {
86+
numEnd++
87+
}
88+
if numEnd == 0 || numEnd >= len(s) {
89+
return d, fmt.Errorf("FORG0001: invalid duration format: %q", orig)
90+
}
91+
numStr := s[:numEnd]
92+
designator := s[numEnd]
93+
s = s[numEnd+1:]
94+
95+
if designator == 'S' || strings.Contains(numStr, ".") {
96+
f, err := strconv.ParseFloat(numStr, 64)
97+
if err != nil {
98+
return d, fmt.Errorf("FORG0001: invalid number in duration: %q", orig)
99+
}
100+
if designator == 'S' {
101+
d.Seconds = f
102+
} else {
103+
return d, fmt.Errorf("FORG0001: decimal only allowed for seconds: %q", orig)
104+
}
105+
} else {
106+
n, err := strconv.Atoi(numStr)
107+
if err != nil {
108+
return d, fmt.Errorf("FORG0001: invalid number in duration: %q", orig)
109+
}
110+
switch designator {
111+
case 'Y':
112+
d.Years = n
113+
case 'M':
114+
if inTimePart {
115+
d.Minutes = n
116+
} else {
117+
d.Months = n
118+
}
119+
case 'D':
120+
d.Days = n
121+
case 'H':
122+
d.Hours = n
123+
default:
124+
return d, fmt.Errorf("FORG0001: unexpected designator '%c' in duration: %q", designator, orig)
125+
}
126+
}
127+
}
128+
return d, nil
129+
}
130+
131+
// XSQName represents an xs:QName value with namespace URI, prefix, and local name.
132+
type XSQName struct {
133+
Namespace string
134+
Prefix string
135+
Localname string
136+
}
137+
138+
func (q XSQName) String() string {
139+
if q.Prefix != "" {
140+
return q.Prefix + ":" + q.Localname
141+
}
142+
return q.Localname
143+
}
144+
13145
// ToXSInteger converts the item to an xs:integer.
14146
func ToXSInteger(itm Item) (int, error) {
15147
switch t := itm.(type) {
@@ -38,6 +170,95 @@ func xsTime(ctx *Context, args []Sequence) (Sequence, error) {
38170
return Sequence{XSTime(t)}, nil
39171
}
40172

173+
func xsDouble(ctx *Context, args []Sequence) (Sequence, error) {
174+
nv, err := NumberValue(args[0])
175+
if err != nil {
176+
return nil, err
177+
}
178+
return Sequence{nv}, nil
179+
}
180+
181+
func xsDate(ctx *Context, args []Sequence) (Sequence, error) {
182+
firstarg, err := StringValue(args[0])
183+
if err != nil {
184+
return nil, err
185+
}
186+
187+
formats := []string{
188+
"2006-01-02-07:00",
189+
"2006-01-02+07:00",
190+
"2006-01-02Z",
191+
"2006-01-02",
192+
}
193+
194+
for _, format := range formats {
195+
t, err := time.Parse(format, firstarg)
196+
if err == nil {
197+
return Sequence{XSDate(t)}, nil
198+
}
199+
}
200+
201+
return nil, fmt.Errorf("FORG0001: cannot cast %q to xs:date", firstarg)
202+
}
203+
204+
func xsInteger(ctx *Context, args []Sequence) (Sequence, error) {
205+
nv, err := NumberValue(args[0])
206+
if err != nil {
207+
return nil, err
208+
}
209+
return Sequence{int(nv)}, nil
210+
}
211+
212+
func xsString(ctx *Context, args []Sequence) (Sequence, error) {
213+
sv, err := StringValue(args[0])
214+
if err != nil {
215+
return nil, err
216+
}
217+
return Sequence{sv}, nil
218+
}
219+
220+
func xsDateTime(ctx *Context, args []Sequence) (Sequence, error) {
221+
firstarg, err := StringValue(args[0])
222+
if err != nil {
223+
return nil, err
224+
}
225+
226+
formats := []string{
227+
"2006-01-02T15:04:05.999999999-07:00",
228+
"2006-01-02T15:04:05.999999999Z",
229+
"2006-01-02T15:04:05.999999999",
230+
"2006-01-02T15:04:05-07:00",
231+
"2006-01-02T15:04:05Z",
232+
"2006-01-02T15:04:05",
233+
}
234+
235+
for _, format := range formats {
236+
t, err := time.Parse(format, firstarg)
237+
if err == nil {
238+
return Sequence{XSDateTime(t)}, nil
239+
}
240+
}
241+
return nil, fmt.Errorf("FORG0001: cannot cast %q to xs:dateTime", firstarg)
242+
}
243+
244+
func xsDuration(ctx *Context, args []Sequence) (Sequence, error) {
245+
firstarg, err := StringValue(args[0])
246+
if err != nil {
247+
return nil, err
248+
}
249+
d, err := ParseXSDuration(firstarg)
250+
if err != nil {
251+
return nil, err
252+
}
253+
return Sequence{d}, nil
254+
}
255+
41256
func init() {
42257
RegisterFunction(&Function{Name: "time", Namespace: nsXS, F: xsTime, MinArg: 1, MaxArg: 1})
258+
RegisterFunction(&Function{Name: "dateTime", Namespace: nsXS, F: xsDateTime, MinArg: 1, MaxArg: 1})
259+
RegisterFunction(&Function{Name: "double", Namespace: nsXS, F: xsDouble, MinArg: 1, MaxArg: 1})
260+
RegisterFunction(&Function{Name: "duration", Namespace: nsXS, F: xsDuration, MinArg: 1, MaxArg: 1})
261+
RegisterFunction(&Function{Name: "date", Namespace: nsXS, F: xsDate, MinArg: 1, MaxArg: 1})
262+
RegisterFunction(&Function{Name: "integer", Namespace: nsXS, F: xsInteger, MinArg: 1, MaxArg: 1})
263+
RegisterFunction(&Function{Name: "string", Namespace: nsXS, F: xsString, MinArg: 1, MaxArg: 1})
43264
}

0 commit comments

Comments
 (0)