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 // if length is given and is negative, then that many characters will be omitted from
// the end of string. // the end of string.
func (ns *Namespace) Substr(a interface{}, nums ...interface{}) (string, error) { func (ns *Namespace) Substr(a interface{}, nums ...interface{}) (string, error) {
aStr, err := cast.ToStringE(a) s, err := cast.ToStringE(a)
if err != nil { if err != nil {
return "", err return "", err
} }
var start, length int asRunes := []rune(s)
rlen := len(asRunes)
asRunes := []rune(aStr) var start, length int
switch len(nums) { switch len(nums) {
case 0: case 0:
return "", errors.New("too less arguments") return "", errors.New("too few arguments")
case 1: case 1:
if start, err = cast.ToIntE(nums[0]); err != nil { 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: case 2:
if start, err = cast.ToIntE(nums[0]); err != nil { 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 { 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: default:
return "", errors.New("too many arguments") 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 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 end := rlen
if start >= 0 && length >= 0 {
s = start if length < 0 {
e = start + length end += length
} else if start < 0 && length >= 0 { } else if length > 0 {
s = len(asRunes) + start - length + 1 end = start + length
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
} }
if s > e { if start >= end {
return "", fmt.Errorf("calculated start position greater than end position: %d > %d", s, e) return "", fmt.Errorf("calculated start position greater than end position: %d > %d", start, end)
}
if e > len(asRunes) {
e = len(asRunes)
} }
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 // 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", 1, 2, "bc"},
{"abc", 0, 1, "a"}, {"abc", 0, 1, "a"},
{"abcdef", -1, 2, "ef"}, {"abcdef", -1, 2, "f"},
{"abcdef", -3, 3, "bcd"}, {"abcdef", -3, 3, "def"},
{"abcdef", -1, nil, "f"},
{"abcdef", -2, nil, "ef"},
{"abcdef", -3, 1, "d"},
{"abcdef", 0, -1, "abcde"}, {"abcdef", 0, -1, "abcde"},
{"abcdef", 2, -1, "cde"}, {"abcdef", 2, -1, "cde"},
{"abcdef", 4, -4, false}, {"abcdef", 4, -4, false},
@ -480,12 +483,12 @@ func TestSubstr(t *testing.T) {
} }
if b, ok := test.expect.(bool); ok && !b { 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 continue
} }
c.Assert(err, qt.IsNil) 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") _, err = ns.Substr("abcdef")