1
0
mirror of https://github.com/golang/go synced 2024-09-29 22:24:33 -06:00

unicode: fix SpecialCase to follow its docs & respect explict no-op mappings

If SpecialCase contains an explicit CaseRange with zero deltas,
respect those and don't fall back to the default behavior.

Fixes #25636

Change-Id: Ic554c6b3dd462b1b39c75194eec469b6ff4aa55b
Reviewed-on: https://go-review.googlesource.com/117155
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
This commit is contained in:
Brad Fitzpatrick 2018-06-07 18:18:50 +00:00 committed by Marcel van Lohuizen
parent b459e00454
commit 05f8b44d5e
2 changed files with 26 additions and 12 deletions

View File

@ -206,9 +206,10 @@ func IsTitle(r rune) bool {
}
// to maps the rune using the specified case mapping.
func to(_case int, r rune, caseRange []CaseRange) rune {
// It additionally reports whether caseRange contained a mapping for r.
func to(_case int, r rune, caseRange []CaseRange) (mappedRune rune, foundMapping bool) {
if _case < 0 || MaxCase <= _case {
return ReplacementChar // as reasonable an error as any
return ReplacementChar, false // as reasonable an error as any
}
// binary search over ranges
lo := 0
@ -229,9 +230,9 @@ func to(_case int, r rune, caseRange []CaseRange) rune {
// bit in the sequence offset.
// The constants UpperCase and TitleCase are even while LowerCase
// is odd so we take the low bit from _case.
return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1))
return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1)), true
}
return r + delta
return r + delta, true
}
if r < rune(cr.Lo) {
hi = m
@ -239,12 +240,13 @@ func to(_case int, r rune, caseRange []CaseRange) rune {
lo = m + 1
}
}
return r
return r, false
}
// To maps the rune to the specified case: UpperCase, LowerCase, or TitleCase.
func To(_case int, r rune) rune {
return to(_case, r, CaseRanges)
r, _ = to(_case, r, CaseRanges)
return r
}
// ToUpper maps the rune to upper case.
@ -282,8 +284,8 @@ func ToTitle(r rune) rune {
// ToUpper maps the rune to upper case giving priority to the special mapping.
func (special SpecialCase) ToUpper(r rune) rune {
r1 := to(UpperCase, r, []CaseRange(special))
if r1 == r {
r1, hadMapping := to(UpperCase, r, []CaseRange(special))
if r1 == r && !hadMapping {
r1 = ToUpper(r)
}
return r1
@ -291,8 +293,8 @@ func (special SpecialCase) ToUpper(r rune) rune {
// ToTitle maps the rune to title case giving priority to the special mapping.
func (special SpecialCase) ToTitle(r rune) rune {
r1 := to(TitleCase, r, []CaseRange(special))
if r1 == r {
r1, hadMapping := to(TitleCase, r, []CaseRange(special))
if r1 == r && !hadMapping {
r1 = ToTitle(r)
}
return r1
@ -300,8 +302,8 @@ func (special SpecialCase) ToTitle(r rune) rune {
// ToLower maps the rune to lower case giving priority to the special mapping.
func (special SpecialCase) ToLower(r rune) rune {
r1 := to(LowerCase, r, []CaseRange(special))
if r1 == r {
r1, hadMapping := to(LowerCase, r, []CaseRange(special))
if r1 == r && !hadMapping {
r1 = ToLower(r)
}
return r1

View File

@ -9,6 +9,7 @@ import (
"fmt"
"runtime"
"sort"
"strings"
"testing"
. "unicode"
)
@ -551,3 +552,14 @@ func TestLatinOffset(t *testing.T) {
}
}
}
func TestSpecialCaseNoMapping(t *testing.T) {
// Issue 25636
// no change for rune 'A', zero delta, under upper/lower/title case change.
var noChangeForCapitalA = CaseRange{'A', 'A', [MaxCase]rune{0, 0, 0}}
got := strings.ToLowerSpecial(SpecialCase([]CaseRange{noChangeForCapitalA}), "ABC")
want := "Abc"
if got != want {
t.Errorf("got %q; want %q", got, want)
}
}