| // Copyright 2014 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package cases | |
| // This file contains the definitions of case mappings for all supported | |
| // languages. The rules for the language-specific tailorings were taken and | |
| // modified from the CLDR transform definitions in common/transforms. | |
| import ( | |
| "strings" | |
| "unicode" | |
| "unicode/utf8" | |
| "golang.org/x/text/internal" | |
| "golang.org/x/text/language" | |
| "golang.org/x/text/transform" | |
| "golang.org/x/text/unicode/norm" | |
| ) | |
| // A mapFunc takes a context set to the current rune and writes the mapped | |
| // version to the same context. It may advance the context to the next rune. It | |
| // returns whether a checkpoint is possible: whether the pDst bytes written to | |
| // dst so far won't need changing as we see more source bytes. | |
| type mapFunc func(*context) bool | |
| // A spanFunc takes a context set to the current rune and returns whether this | |
| // rune would be altered when written to the output. It may advance the context | |
| // to the next rune. It returns whether a checkpoint is possible. | |
| type spanFunc func(*context) bool | |
| // maxIgnorable defines the maximum number of ignorables to consider for | |
| // lookahead operations. | |
| const maxIgnorable = 30 | |
| // supported lists the language tags for which we have tailorings. | |
| const supported = "und af az el lt nl tr" | |
| func init() { | |
| tags := []language.Tag{} | |
| for _, s := range strings.Split(supported, " ") { | |
| tags = append(tags, language.MustParse(s)) | |
| } | |
| matcher = internal.NewInheritanceMatcher(tags) | |
| Supported = language.NewCoverage(tags) | |
| } | |
| var ( | |
| matcher *internal.InheritanceMatcher | |
| Supported language.Coverage | |
| // We keep the following lists separate, instead of having a single per- | |
| // language struct, to give the compiler a chance to remove unused code. | |
| // Some uppercase mappers are stateless, so we can precompute the | |
| // Transformers and save a bit on runtime allocations. | |
| upperFunc = []struct { | |
| upper mapFunc | |
| span spanFunc | |
| }{ | |
| {nil, nil}, // und | |
| {nil, nil}, // af | |
| {aztrUpper(upper), isUpper}, // az | |
| {elUpper, noSpan}, // el | |
| {ltUpper(upper), noSpan}, // lt | |
| {nil, nil}, // nl | |
| {aztrUpper(upper), isUpper}, // tr | |
| } | |
| undUpper transform.SpanningTransformer = &undUpperCaser{} | |
| undLower transform.SpanningTransformer = &undLowerCaser{} | |
| undLowerIgnoreSigma transform.SpanningTransformer = &undLowerIgnoreSigmaCaser{} | |
| lowerFunc = []mapFunc{ | |
| nil, // und | |
| nil, // af | |
| aztrLower, // az | |
| nil, // el | |
| ltLower, // lt | |
| nil, // nl | |
| aztrLower, // tr | |
| } | |
| titleInfos = []struct { | |
| title mapFunc | |
| lower mapFunc | |
| titleSpan spanFunc | |
| rewrite func(*context) | |
| }{ | |
| {title, lower, isTitle, nil}, // und | |
| {title, lower, isTitle, afnlRewrite}, // af | |
| {aztrUpper(title), aztrLower, isTitle, nil}, // az | |
| {title, lower, isTitle, nil}, // el | |
| {ltUpper(title), ltLower, noSpan, nil}, // lt | |
| {nlTitle, lower, nlTitleSpan, afnlRewrite}, // nl | |
| {aztrUpper(title), aztrLower, isTitle, nil}, // tr | |
| } | |
| ) | |
| func makeUpper(t language.Tag, o options) transform.SpanningTransformer { | |
| _, i, _ := matcher.Match(t) | |
| f := upperFunc[i].upper | |
| if f == nil { | |
| return undUpper | |
| } | |
| return &simpleCaser{f: f, span: upperFunc[i].span} | |
| } | |
| func makeLower(t language.Tag, o options) transform.SpanningTransformer { | |
| _, i, _ := matcher.Match(t) | |
| f := lowerFunc[i] | |
| if f == nil { | |
| if o.ignoreFinalSigma { | |
| return undLowerIgnoreSigma | |
| } | |
| return undLower | |
| } | |
| if o.ignoreFinalSigma { | |
| return &simpleCaser{f: f, span: isLower} | |
| } | |
| return &lowerCaser{ | |
| first: f, | |
| midWord: finalSigma(f), | |
| } | |
| } | |
| func makeTitle(t language.Tag, o options) transform.SpanningTransformer { | |
| _, i, _ := matcher.Match(t) | |
| x := &titleInfos[i] | |
| lower := x.lower | |
| if o.noLower { | |
| lower = (*context).copy | |
| } else if !o.ignoreFinalSigma { | |
| lower = finalSigma(lower) | |
| } | |
| return &titleCaser{ | |
| title: x.title, | |
| lower: lower, | |
| titleSpan: x.titleSpan, | |
| rewrite: x.rewrite, | |
| } | |
| } | |
| func noSpan(c *context) bool { | |
| c.err = transform.ErrEndOfSpan | |
| return false | |
| } | |
| // TODO: consider a similar special case for the fast majority lower case. This | |
| // is a bit more involved so will require some more precise benchmarking to | |
| // justify it. | |
| type undUpperCaser struct{ transform.NopResetter } | |
| // undUpperCaser implements the Transformer interface for doing an upper case | |
| // mapping for the root locale (und). It eliminates the need for an allocation | |
| // as it prevents escaping by not using function pointers. | |
| func (t undUpperCaser) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { | |
| c := context{dst: dst, src: src, atEOF: atEOF} | |
| for c.next() { | |
| upper(&c) | |
| c.checkpoint() | |
| } | |
| return c.ret() | |
| } | |
| func (t undUpperCaser) Span(src []byte, atEOF bool) (n int, err error) { | |
| c := context{src: src, atEOF: atEOF} | |
| for c.next() && isUpper(&c) { | |
| c.checkpoint() | |
| } | |
| return c.retSpan() | |
| } | |
| // undLowerIgnoreSigmaCaser implements the Transformer interface for doing | |
| // a lower case mapping for the root locale (und) ignoring final sigma | |
| // handling. This casing algorithm is used in some performance-critical packages | |
| // like secure/precis and x/net/http/idna, which warrants its special-casing. | |
| type undLowerIgnoreSigmaCaser struct{ transform.NopResetter } | |
| func (t undLowerIgnoreSigmaCaser) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { | |
| c := context{dst: dst, src: src, atEOF: atEOF} | |
| for c.next() && lower(&c) { | |
| c.checkpoint() | |
| } | |
| return c.ret() | |
| } | |
| // Span implements a generic lower-casing. This is possible as isLower works | |
| // for all lowercasing variants. All lowercase variants only vary in how they | |
| // transform a non-lowercase letter. They will never change an already lowercase | |
| // letter. In addition, there is no state. | |
| func (t undLowerIgnoreSigmaCaser) Span(src []byte, atEOF bool) (n int, err error) { | |
| c := context{src: src, atEOF: atEOF} | |
| for c.next() && isLower(&c) { | |
| c.checkpoint() | |
| } | |
| return c.retSpan() | |
| } | |
| type simpleCaser struct { | |
| context | |
| f mapFunc | |
| span spanFunc | |
| } | |
| // simpleCaser implements the Transformer interface for doing a case operation | |
| // on a rune-by-rune basis. | |
| func (t *simpleCaser) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { | |
| c := context{dst: dst, src: src, atEOF: atEOF} | |
| for c.next() && t.f(&c) { | |
| c.checkpoint() | |
| } | |
| return c.ret() | |
| } | |
| func (t *simpleCaser) Span(src []byte, atEOF bool) (n int, err error) { | |
| c := context{src: src, atEOF: atEOF} | |
| for c.next() && t.span(&c) { | |
| c.checkpoint() | |
| } | |
| return c.retSpan() | |
| } | |
| // undLowerCaser implements the Transformer interface for doing a lower case | |
| // mapping for the root locale (und) ignoring final sigma handling. This casing | |
| // algorithm is used in some performance-critical packages like secure/precis | |
| // and x/net/http/idna, which warrants its special-casing. | |
| type undLowerCaser struct{ transform.NopResetter } | |
| func (t undLowerCaser) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { | |
| c := context{dst: dst, src: src, atEOF: atEOF} | |
| for isInterWord := true; c.next(); { | |
| if isInterWord { | |
| if c.info.isCased() { | |
| if !lower(&c) { | |
| break | |
| } | |
| isInterWord = false | |
| } else if !c.copy() { | |
| break | |
| } | |
| } else { | |
| if c.info.isNotCasedAndNotCaseIgnorable() { | |
| if !c.copy() { | |
| break | |
| } | |
| isInterWord = true | |
| } else if !c.hasPrefix("Σ") { | |
| if !lower(&c) { | |
| break | |
| } | |
| } else if !finalSigmaBody(&c) { | |
| break | |
| } | |
| } | |
| c.checkpoint() | |
| } | |
| return c.ret() | |
| } | |
| func (t undLowerCaser) Span(src []byte, atEOF bool) (n int, err error) { | |
| c := context{src: src, atEOF: atEOF} | |
| for c.next() && isLower(&c) { | |
| c.checkpoint() | |
| } | |
| return c.retSpan() | |
| } | |
| // lowerCaser implements the Transformer interface. The default Unicode lower | |
| // casing requires different treatment for the first and subsequent characters | |
| // of a word, most notably to handle the Greek final Sigma. | |
| type lowerCaser struct { | |
| undLowerIgnoreSigmaCaser | |
| context | |
| first, midWord mapFunc | |
| } | |
| func (t *lowerCaser) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { | |
| t.context = context{dst: dst, src: src, atEOF: atEOF} | |
| c := &t.context | |
| for isInterWord := true; c.next(); { | |
| if isInterWord { | |
| if c.info.isCased() { | |
| if !t.first(c) { | |
| break | |
| } | |
| isInterWord = false | |
| } else if !c.copy() { | |
| break | |
| } | |
| } else { | |
| if c.info.isNotCasedAndNotCaseIgnorable() { | |
| if !c.copy() { | |
| break | |
| } | |
| isInterWord = true | |
| } else if !t.midWord(c) { | |
| break | |
| } | |
| } | |
| c.checkpoint() | |
| } | |
| return c.ret() | |
| } | |
| // titleCaser implements the Transformer interface. Title casing algorithms | |
| // distinguish between the first letter of a word and subsequent letters of the | |
| // same word. It uses state to avoid requiring a potentially infinite lookahead. | |
| type titleCaser struct { | |
| context | |
| // rune mappings used by the actual casing algorithms. | |
| title mapFunc | |
| lower mapFunc | |
| titleSpan spanFunc | |
| rewrite func(*context) | |
| } | |
| // Transform implements the standard Unicode title case algorithm as defined in | |
| // Chapter 3 of The Unicode Standard: | |
| // toTitlecase(X): Find the word boundaries in X according to Unicode Standard | |
| // Annex #29, "Unicode Text Segmentation." For each word boundary, find the | |
| // first cased character F following the word boundary. If F exists, map F to | |
| // Titlecase_Mapping(F); then map all characters C between F and the following | |
| // word boundary to Lowercase_Mapping(C). | |
| func (t *titleCaser) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { | |
| t.context = context{dst: dst, src: src, atEOF: atEOF, isMidWord: t.isMidWord} | |
| c := &t.context | |
| if !c.next() { | |
| return c.ret() | |
| } | |
| for { | |
| p := c.info | |
| if t.rewrite != nil { | |
| t.rewrite(c) | |
| } | |
| wasMid := p.isMid() | |
| // Break out of this loop on failure to ensure we do not modify the | |
| // state incorrectly. | |
| if p.isCased() { | |
| if !c.isMidWord { | |
| if !t.title(c) { | |
| break | |
| } | |
| c.isMidWord = true | |
| } else if !t.lower(c) { | |
| break | |
| } | |
| } else if !c.copy() { | |
| break | |
| } else if p.isBreak() { | |
| c.isMidWord = false | |
| } | |
| // As we save the state of the transformer, it is safe to call | |
| // checkpoint after any successful write. | |
| if !(c.isMidWord && wasMid) { | |
| c.checkpoint() | |
| } | |
| if !c.next() { | |
| break | |
| } | |
| if wasMid && c.info.isMid() { | |
| c.isMidWord = false | |
| } | |
| } | |
| return c.ret() | |
| } | |
| func (t *titleCaser) Span(src []byte, atEOF bool) (n int, err error) { | |
| t.context = context{src: src, atEOF: atEOF, isMidWord: t.isMidWord} | |
| c := &t.context | |
| if !c.next() { | |
| return c.retSpan() | |
| } | |
| for { | |
| p := c.info | |
| if t.rewrite != nil { | |
| t.rewrite(c) | |
| } | |
| wasMid := p.isMid() | |
| // Break out of this loop on failure to ensure we do not modify the | |
| // state incorrectly. | |
| if p.isCased() { | |
| if !c.isMidWord { | |
| if !t.titleSpan(c) { | |
| break | |
| } | |
| c.isMidWord = true | |
| } else if !isLower(c) { | |
| break | |
| } | |
| } else if p.isBreak() { | |
| c.isMidWord = false | |
| } | |
| // As we save the state of the transformer, it is safe to call | |
| // checkpoint after any successful write. | |
| if !(c.isMidWord && wasMid) { | |
| c.checkpoint() | |
| } | |
| if !c.next() { | |
| break | |
| } | |
| if wasMid && c.info.isMid() { | |
| c.isMidWord = false | |
| } | |
| } | |
| return c.retSpan() | |
| } | |
| // finalSigma adds Greek final Sigma handing to another casing function. It | |
| // determines whether a lowercased sigma should be σ or ς, by looking ahead for | |
| // case-ignorables and a cased letters. | |
| func finalSigma(f mapFunc) mapFunc { | |
| return func(c *context) bool { | |
| if !c.hasPrefix("Σ") { | |
| return f(c) | |
| } | |
| return finalSigmaBody(c) | |
| } | |
| } | |
| func finalSigmaBody(c *context) bool { | |
| // Current rune must be ∑. | |
| // ::NFD(); | |
| // # 03A3; 03C2; 03A3; 03A3; Final_Sigma; # GREEK CAPITAL LETTER SIGMA | |
| // Σ } [:case-ignorable:]* [:cased:] → σ; | |
| // [:cased:] [:case-ignorable:]* { Σ → ς; | |
| // ::Any-Lower; | |
| // ::NFC(); | |
| p := c.pDst | |
| c.writeString("ς") | |
| // TODO: we should do this here, but right now this will never have an | |
| // effect as this is called when the prefix is Sigma, whereas Dutch and | |
| // Afrikaans only test for an apostrophe. | |
| // | |
| // if t.rewrite != nil { | |
| // t.rewrite(c) | |
| // } | |
| // We need to do one more iteration after maxIgnorable, as a cased | |
| // letter is not an ignorable and may modify the result. | |
| wasMid := false | |
| for i := 0; i < maxIgnorable+1; i++ { | |
| if !c.next() { | |
| return false | |
| } | |
| if !c.info.isCaseIgnorable() { | |
| // All Midword runes are also case ignorable, so we are | |
| // guaranteed to have a letter or word break here. As we are | |
| // unreading the run, there is no need to unset c.isMidWord; | |
| // the title caser will handle this. | |
| if c.info.isCased() { | |
| // p+1 is guaranteed to be in bounds: if writing ς was | |
| // successful, p+1 will contain the second byte of ς. If not, | |
| // this function will have returned after c.next returned false. | |
| c.dst[p+1]++ // ς → σ | |
| } | |
| c.unreadRune() | |
| return true | |
| } | |
| // A case ignorable may also introduce a word break, so we may need | |
| // to continue searching even after detecting a break. | |
| isMid := c.info.isMid() | |
| if (wasMid && isMid) || c.info.isBreak() { | |
| c.isMidWord = false | |
| } | |
| wasMid = isMid | |
| c.copy() | |
| } | |
| return true | |
| } | |
| // finalSigmaSpan would be the same as isLower. | |
| // elUpper implements Greek upper casing, which entails removing a predefined | |
| // set of non-blocked modifiers. Note that these accents should not be removed | |
| // for title casing! | |
| // Example: "Οδός" -> "ΟΔΟΣ". | |
| func elUpper(c *context) bool { | |
| // From CLDR: | |
| // [:Greek:] [^[:ccc=Not_Reordered:][:ccc=Above:]]*? { [\u0313\u0314\u0301\u0300\u0306\u0342\u0308\u0304] → ; | |
| // [:Greek:] [^[:ccc=Not_Reordered:][:ccc=Iota_Subscript:]]*? { \u0345 → ; | |
| r, _ := utf8.DecodeRune(c.src[c.pSrc:]) | |
| oldPDst := c.pDst | |
| if !upper(c) { | |
| return false | |
| } | |
| if !unicode.Is(unicode.Greek, r) { | |
| return true | |
| } | |
| i := 0 | |
| // Take the properties of the uppercased rune that is already written to the | |
| // destination. This saves us the trouble of having to uppercase the | |
| // decomposed rune again. | |
| if b := norm.NFD.Properties(c.dst[oldPDst:]).Decomposition(); b != nil { | |
| // Restore the destination position and process the decomposed rune. | |
| r, sz := utf8.DecodeRune(b) | |
| if r <= 0xFF { // See A.6.1 | |
| return true | |
| } | |
| c.pDst = oldPDst | |
| // Insert the first rune and ignore the modifiers. See A.6.2. | |
| c.writeBytes(b[:sz]) | |
| i = len(b[sz:]) / 2 // Greek modifiers are always of length 2. | |
| } | |
| for ; i < maxIgnorable && c.next(); i++ { | |
| switch r, _ := utf8.DecodeRune(c.src[c.pSrc:]); r { | |
| // Above and Iota Subscript | |
| case 0x0300, // U+0300 COMBINING GRAVE ACCENT | |
| 0x0301, // U+0301 COMBINING ACUTE ACCENT | |
| 0x0304, // U+0304 COMBINING MACRON | |
| 0x0306, // U+0306 COMBINING BREVE | |
| 0x0308, // U+0308 COMBINING DIAERESIS | |
| 0x0313, // U+0313 COMBINING COMMA ABOVE | |
| 0x0314, // U+0314 COMBINING REVERSED COMMA ABOVE | |
| 0x0342, // U+0342 COMBINING GREEK PERISPOMENI | |
| 0x0345: // U+0345 COMBINING GREEK YPOGEGRAMMENI | |
| // No-op. Gobble the modifier. | |
| default: | |
| switch v, _ := trie.lookup(c.src[c.pSrc:]); info(v).cccType() { | |
| case cccZero: | |
| c.unreadRune() | |
| return true | |
| // We don't need to test for IotaSubscript as the only rune that | |
| // qualifies (U+0345) was already excluded in the switch statement | |
| // above. See A.4. | |
| case cccAbove: | |
| return c.copy() | |
| default: | |
| // Some other modifier. We're still allowed to gobble Greek | |
| // modifiers after this. | |
| c.copy() | |
| } | |
| } | |
| } | |
| return i == maxIgnorable | |
| } | |
| // TODO: implement elUpperSpan (low-priority: complex and infrequent). | |
| func ltLower(c *context) bool { | |
| // From CLDR: | |
| // # Introduce an explicit dot above when lowercasing capital I's and J's | |
| // # whenever there are more accents above. | |
| // # (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek) | |
| // # 0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I | |
| // # 004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J | |
| // # 012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK | |
| // # 00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE | |
| // # 00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE | |
| // # 0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE | |
| // ::NFD(); | |
| // I } [^[:ccc=Not_Reordered:][:ccc=Above:]]* [:ccc=Above:] → i \u0307; | |
| // J } [^[:ccc=Not_Reordered:][:ccc=Above:]]* [:ccc=Above:] → j \u0307; | |
| // I \u0328 (Į) } [^[:ccc=Not_Reordered:][:ccc=Above:]]* [:ccc=Above:] → i \u0328 \u0307; | |
| // I \u0300 (Ì) → i \u0307 \u0300; | |
| // I \u0301 (Í) → i \u0307 \u0301; | |
| // I \u0303 (Ĩ) → i \u0307 \u0303; | |
| // ::Any-Lower(); | |
| // ::NFC(); | |
| i := 0 | |
| if r := c.src[c.pSrc]; r < utf8.RuneSelf { | |
| lower(c) | |
| if r != 'I' && r != 'J' { | |
| return true | |
| } | |
| } else { | |
| p := norm.NFD.Properties(c.src[c.pSrc:]) | |
| if d := p.Decomposition(); len(d) >= 3 && (d[0] == 'I' || d[0] == 'J') { | |
| // UTF-8 optimization: the decomposition will only have an above | |
| // modifier if the last rune of the decomposition is in [U+300-U+311]. | |
| // In all other cases, a decomposition starting with I is always | |
| // an I followed by modifiers that are not cased themselves. See A.2. | |
| if d[1] == 0xCC && d[2] <= 0x91 { // A.2.4. | |
| if !c.writeBytes(d[:1]) { | |
| return false | |
| } | |
| c.dst[c.pDst-1] += 'a' - 'A' // lower | |
| // Assumption: modifier never changes on lowercase. See A.1. | |
| // Assumption: all modifiers added have CCC = Above. See A.2.3. | |
| return c.writeString("\u0307") && c.writeBytes(d[1:]) | |
| } | |
| // In all other cases the additional modifiers will have a CCC | |
| // that is less than 230 (Above). We will insert the U+0307, if | |
| // needed, after these modifiers so that a string in FCD form | |
| // will remain so. See A.2.2. | |
| lower(c) | |
| i = 1 | |
| } else { | |
| return lower(c) | |
| } | |
| } | |
| for ; i < maxIgnorable && c.next(); i++ { | |
| switch c.info.cccType() { | |
| case cccZero: | |
| c.unreadRune() | |
| return true | |
| case cccAbove: | |
| return c.writeString("\u0307") && c.copy() // See A.1. | |
| default: | |
| c.copy() // See A.1. | |
| } | |
| } | |
| return i == maxIgnorable | |
| } | |
| // ltLowerSpan would be the same as isLower. | |
| func ltUpper(f mapFunc) mapFunc { | |
| return func(c *context) bool { | |
| // Unicode: | |
| // 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE | |
| // | |
| // From CLDR: | |
| // # Remove \u0307 following soft-dotteds (i, j, and the like), with possible | |
| // # intervening non-230 marks. | |
| // ::NFD(); | |
| // [:Soft_Dotted:] [^[:ccc=Not_Reordered:][:ccc=Above:]]* { \u0307 → ; | |
| // ::Any-Upper(); | |
| // ::NFC(); | |
| // TODO: See A.5. A soft-dotted rune never has an exception. This would | |
| // allow us to overload the exception bit and encode this property in | |
| // info. Need to measure performance impact of this. | |
| r, _ := utf8.DecodeRune(c.src[c.pSrc:]) | |
| oldPDst := c.pDst | |
| if !f(c) { | |
| return false | |
| } | |
| if !unicode.Is(unicode.Soft_Dotted, r) { | |
| return true | |
| } | |
| // We don't need to do an NFD normalization, as a soft-dotted rune never | |
| // contains U+0307. See A.3. | |
| i := 0 | |
| for ; i < maxIgnorable && c.next(); i++ { | |
| switch c.info.cccType() { | |
| case cccZero: | |
| c.unreadRune() | |
| return true | |
| case cccAbove: | |
| if c.hasPrefix("\u0307") { | |
| // We don't do a full NFC, but rather combine runes for | |
| // some of the common cases. (Returning NFC or | |
| // preserving normal form is neither a requirement nor | |
| // a possibility anyway). | |
| if !c.next() { | |
| return false | |
| } | |
| if c.dst[oldPDst] == 'I' && c.pDst == oldPDst+1 && c.src[c.pSrc] == 0xcc { | |
| s := "" | |
| switch c.src[c.pSrc+1] { | |
| case 0x80: // U+0300 COMBINING GRAVE ACCENT | |
| s = "\u00cc" // U+00CC LATIN CAPITAL LETTER I WITH GRAVE | |
| case 0x81: // U+0301 COMBINING ACUTE ACCENT | |
| s = "\u00cd" // U+00CD LATIN CAPITAL LETTER I WITH ACUTE | |
| case 0x83: // U+0303 COMBINING TILDE | |
| s = "\u0128" // U+0128 LATIN CAPITAL LETTER I WITH TILDE | |
| case 0x88: // U+0308 COMBINING DIAERESIS | |
| s = "\u00cf" // U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS | |
| default: | |
| } | |
| if s != "" { | |
| c.pDst = oldPDst | |
| return c.writeString(s) | |
| } | |
| } | |
| } | |
| return c.copy() | |
| default: | |
| c.copy() | |
| } | |
| } | |
| return i == maxIgnorable | |
| } | |
| } | |
| // TODO: implement ltUpperSpan (low priority: complex and infrequent). | |
| func aztrUpper(f mapFunc) mapFunc { | |
| return func(c *context) bool { | |
| // i→İ; | |
| if c.src[c.pSrc] == 'i' { | |
| return c.writeString("İ") | |
| } | |
| return f(c) | |
| } | |
| } | |
| func aztrLower(c *context) (done bool) { | |
| // From CLDR: | |
| // # I and i-dotless; I-dot and i are case pairs in Turkish and Azeri | |
| // # 0130; 0069; 0130; 0130; tr; # LATIN CAPITAL LETTER I WITH DOT ABOVE | |
| // İ→i; | |
| // # When lowercasing, remove dot_above in the sequence I + dot_above, which will turn into i. | |
| // # This matches the behavior of the canonically equivalent I-dot_above | |
| // # 0307; ; 0307; 0307; tr After_I; # COMBINING DOT ABOVE | |
| // # When lowercasing, unless an I is before a dot_above, it turns into a dotless i. | |
| // # 0049; 0131; 0049; 0049; tr Not_Before_Dot; # LATIN CAPITAL LETTER I | |
| // I([^[:ccc=Not_Reordered:][:ccc=Above:]]*)\u0307 → i$1 ; | |
| // I→ı ; | |
| // ::Any-Lower(); | |
| if c.hasPrefix("\u0130") { // İ | |
| return c.writeString("i") | |
| } | |
| if c.src[c.pSrc] != 'I' { | |
| return lower(c) | |
| } | |
| // We ignore the lower-case I for now, but insert it later when we know | |
| // which form we need. | |
| start := c.pSrc + c.sz | |
| i := 0 | |
| Loop: | |
| // We check for up to n ignorables before \u0307. As \u0307 is an | |
| // ignorable as well, n is maxIgnorable-1. | |
| for ; i < maxIgnorable && c.next(); i++ { | |
| switch c.info.cccType() { | |
| case cccAbove: | |
| if c.hasPrefix("\u0307") { | |
| return c.writeString("i") && c.writeBytes(c.src[start:c.pSrc]) // ignore U+0307 | |
| } | |
| done = true | |
| break Loop | |
| case cccZero: | |
| c.unreadRune() | |
| done = true | |
| break Loop | |
| default: | |
| // We'll write this rune after we know which starter to use. | |
| } | |
| } | |
| if i == maxIgnorable { | |
| done = true | |
| } | |
| return c.writeString("ı") && c.writeBytes(c.src[start:c.pSrc+c.sz]) && done | |
| } | |
| // aztrLowerSpan would be the same as isLower. | |
| func nlTitle(c *context) bool { | |
| // From CLDR: | |
| // # Special titlecasing for Dutch initial "ij". | |
| // ::Any-Title(); | |
| // # Fix up Ij at the beginning of a "word" (per Any-Title, notUAX #29) | |
| // [:^WB=ALetter:] [:WB=Extend:]* [[:WB=MidLetter:][:WB=MidNumLet:]]? { Ij } → IJ ; | |
| if c.src[c.pSrc] != 'I' && c.src[c.pSrc] != 'i' { | |
| return title(c) | |
| } | |
| if !c.writeString("I") || !c.next() { | |
| return false | |
| } | |
| if c.src[c.pSrc] == 'j' || c.src[c.pSrc] == 'J' { | |
| return c.writeString("J") | |
| } | |
| c.unreadRune() | |
| return true | |
| } | |
| func nlTitleSpan(c *context) bool { | |
| // From CLDR: | |
| // # Special titlecasing for Dutch initial "ij". | |
| // ::Any-Title(); | |
| // # Fix up Ij at the beginning of a "word" (per Any-Title, notUAX #29) | |
| // [:^WB=ALetter:] [:WB=Extend:]* [[:WB=MidLetter:][:WB=MidNumLet:]]? { Ij } → IJ ; | |
| if c.src[c.pSrc] != 'I' { | |
| return isTitle(c) | |
| } | |
| if !c.next() || c.src[c.pSrc] == 'j' { | |
| return false | |
| } | |
| if c.src[c.pSrc] != 'J' { | |
| c.unreadRune() | |
| } | |
| return true | |
| } | |
| // Not part of CLDR, but see http://unicode.org/cldr/trac/ticket/7078. | |
| func afnlRewrite(c *context) { | |
| if c.hasPrefix("'") || c.hasPrefix("’") { | |
| c.isMidWord = true | |
| } | |
| } |