tpl: Refactor and fix substr logic

Fix miscalculations when start is negative.  Results should now match
PHP substr.

Fixes #7993
This commit is contained in:
Cameron Moore 2020-11-27 23:43:01 -06:00 committed by Bjørn Erik Pedersen
parent 32d4bf68da
commit 64789fb5dc
2 changed files with 46 additions and 34 deletions

View file

@ -279,64 +279,73 @@ func (ns *Namespace) Split(a interface{}, delimiter string) ([]string, error) {
// if length is given and is negative, then that many characters will be omitted from
// the end of string.
func (ns *Namespace) Substr(a interface{}, nums ...interface{}) (string, error) {
aStr, err := cast.ToStringE(a)
s, err := cast.ToStringE(a)
if err != nil {
return "", err
}
var start, length int
asRunes := []rune(s)
rlen := len(asRunes)
asRunes := []rune(aStr)
var start, length int
switch len(nums) {
case 0:
return "", errors.New("too less arguments")
return "", errors.New("too few arguments")
case 1:
if start, err = cast.ToIntE(nums[0]); err != nil {
return "", errors.New("start argument must be integer")
return "", errors.New("start argument must be an integer")
}
length = len(asRunes)
length = rlen
case 2:
if start, err = cast.ToIntE(nums[0]); err != nil {
return "", errors.New("start argument must be integer")
return "", errors.New("start argument must be an integer")
}
if length, err = cast.ToIntE(nums[1]); err != nil {
return "", errors.New("length argument must be integer")
return "", errors.New("length argument must be an integer")
}
default:
return "", errors.New("too many arguments")
}
if start < -len(asRunes) {
if rlen == 0 {
return "", nil
}
if start < 0 {
start += rlen
}
// start was originally negative beyond rlen
if start < 0 {
start = 0
}
if start > len(asRunes) {
return "", fmt.Errorf("start position out of bounds for %d-byte string", len(aStr))
if start > rlen-1 {
return "", fmt.Errorf("start position out of bounds for %d-byte string", rlen)
}
var s, e int
if start >= 0 && length >= 0 {
s = start
e = start + length
} else if start < 0 && length >= 0 {
s = len(asRunes) + start - length + 1
e = len(asRunes) + start + 1
} else if start >= 0 && length < 0 {
s = start
e = len(asRunes) + length
} else {
s = len(asRunes) + start
e = len(asRunes) + length
end := rlen
if length < 0 {
end += length
} else if length > 0 {
end = start + length
}
if s > e {
return "", fmt.Errorf("calculated start position greater than end position: %d > %d", s, e)
}
if e > len(asRunes) {
e = len(asRunes)
if start >= end {
return "", fmt.Errorf("calculated start position greater than end position: %d > %d", start, end)
}
return string(asRunes[s:e]), nil
if end < 0 {
return "", nil
}
if end > rlen {
end = rlen
}
return string(asRunes[start:end]), nil
}
// Title returns a copy of the input s with all Unicode letters that begin words

View file

@ -441,8 +441,11 @@ func TestSubstr(t *testing.T) {
}{
{"abc", 1, 2, "bc"},
{"abc", 0, 1, "a"},
{"abcdef", -1, 2, "ef"},
{"abcdef", -3, 3, "bcd"},
{"abcdef", -1, 2, "f"},
{"abcdef", -3, 3, "def"},
{"abcdef", -1, nil, "f"},
{"abcdef", -2, nil, "ef"},
{"abcdef", -3, 1, "d"},
{"abcdef", 0, -1, "abcde"},
{"abcdef", 2, -1, "cde"},
{"abcdef", 4, -4, false},
@ -480,12 +483,12 @@ func TestSubstr(t *testing.T) {
}
if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err, qt.Not(qt.IsNil), qt.Commentf("%v", test))
continue
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
c.Assert(result, qt.Equals, test.expect, qt.Commentf("%v", test))
}
_, err = ns.Substr("abcdef")