Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
6.2 KiB
262 lines
6.2 KiB
// Copyright 2015 go-swagger maintainers |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
package swag |
|
|
|
import ( |
|
"unicode" |
|
) |
|
|
|
var nameReplaceTable = map[rune]string{ |
|
'@': "At ", |
|
'&': "And ", |
|
'|': "Pipe ", |
|
'$': "Dollar ", |
|
'!': "Bang ", |
|
'-': "", |
|
'_': "", |
|
} |
|
|
|
type ( |
|
splitter struct { |
|
postSplitInitialismCheck bool |
|
initialisms []string |
|
} |
|
|
|
splitterOption func(*splitter) *splitter |
|
) |
|
|
|
// split calls the splitter; splitter provides more control and post options |
|
func split(str string) []string { |
|
lexems := newSplitter().split(str) |
|
result := make([]string, 0, len(lexems)) |
|
|
|
for _, lexem := range lexems { |
|
result = append(result, lexem.GetOriginal()) |
|
} |
|
|
|
return result |
|
|
|
} |
|
|
|
func (s *splitter) split(str string) []nameLexem { |
|
return s.toNameLexems(str) |
|
} |
|
|
|
func newSplitter(options ...splitterOption) *splitter { |
|
splitter := &splitter{ |
|
postSplitInitialismCheck: false, |
|
initialisms: initialisms, |
|
} |
|
|
|
for _, option := range options { |
|
splitter = option(splitter) |
|
} |
|
|
|
return splitter |
|
} |
|
|
|
// withPostSplitInitialismCheck allows to catch initialisms after main split process |
|
func withPostSplitInitialismCheck(s *splitter) *splitter { |
|
s.postSplitInitialismCheck = true |
|
return s |
|
} |
|
|
|
type ( |
|
initialismMatch struct { |
|
start, end int |
|
body []rune |
|
complete bool |
|
} |
|
initialismMatches []*initialismMatch |
|
) |
|
|
|
func (s *splitter) toNameLexems(name string) []nameLexem { |
|
nameRunes := []rune(name) |
|
matches := s.gatherInitialismMatches(nameRunes) |
|
return s.mapMatchesToNameLexems(nameRunes, matches) |
|
} |
|
|
|
func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches { |
|
matches := make(initialismMatches, 0) |
|
|
|
for currentRunePosition, currentRune := range nameRunes { |
|
newMatches := make(initialismMatches, 0, len(matches)) |
|
|
|
// check current initialism matches |
|
for _, match := range matches { |
|
if keepCompleteMatch := match.complete; keepCompleteMatch { |
|
newMatches = append(newMatches, match) |
|
continue |
|
} |
|
|
|
// drop failed match |
|
currentMatchRune := match.body[currentRunePosition-match.start] |
|
if !s.initialismRuneEqual(currentMatchRune, currentRune) { |
|
continue |
|
} |
|
|
|
// try to complete ongoing match |
|
if currentRunePosition-match.start == len(match.body)-1 { |
|
// we are close; the next step is to check the symbol ahead |
|
// if it is a small letter, then it is not the end of match |
|
// but beginning of the next word |
|
|
|
if currentRunePosition < len(nameRunes)-1 { |
|
nextRune := nameRunes[currentRunePosition+1] |
|
if newWord := unicode.IsLower(nextRune); newWord { |
|
// oh ok, it was the start of a new word |
|
continue |
|
} |
|
} |
|
|
|
match.complete = true |
|
match.end = currentRunePosition |
|
} |
|
|
|
newMatches = append(newMatches, match) |
|
} |
|
|
|
// check for new initialism matches |
|
for _, initialism := range s.initialisms { |
|
initialismRunes := []rune(initialism) |
|
if s.initialismRuneEqual(initialismRunes[0], currentRune) { |
|
newMatches = append(newMatches, &initialismMatch{ |
|
start: currentRunePosition, |
|
body: initialismRunes, |
|
complete: false, |
|
}) |
|
} |
|
} |
|
|
|
matches = newMatches |
|
} |
|
|
|
return matches |
|
} |
|
|
|
func (s *splitter) mapMatchesToNameLexems(nameRunes []rune, matches initialismMatches) []nameLexem { |
|
nameLexems := make([]nameLexem, 0) |
|
|
|
var lastAcceptedMatch *initialismMatch |
|
for _, match := range matches { |
|
if !match.complete { |
|
continue |
|
} |
|
|
|
if firstMatch := lastAcceptedMatch == nil; firstMatch { |
|
nameLexems = append(nameLexems, s.breakCasualString(nameRunes[:match.start])...) |
|
nameLexems = append(nameLexems, s.breakInitialism(string(match.body))) |
|
|
|
lastAcceptedMatch = match |
|
|
|
continue |
|
} |
|
|
|
if overlappedMatch := match.start <= lastAcceptedMatch.end; overlappedMatch { |
|
continue |
|
} |
|
|
|
middle := nameRunes[lastAcceptedMatch.end+1 : match.start] |
|
nameLexems = append(nameLexems, s.breakCasualString(middle)...) |
|
nameLexems = append(nameLexems, s.breakInitialism(string(match.body))) |
|
|
|
lastAcceptedMatch = match |
|
} |
|
|
|
// we have not found any accepted matches |
|
if lastAcceptedMatch == nil { |
|
return s.breakCasualString(nameRunes) |
|
} |
|
|
|
if lastAcceptedMatch.end+1 != len(nameRunes) { |
|
rest := nameRunes[lastAcceptedMatch.end+1:] |
|
nameLexems = append(nameLexems, s.breakCasualString(rest)...) |
|
} |
|
|
|
return nameLexems |
|
} |
|
|
|
func (s *splitter) initialismRuneEqual(a, b rune) bool { |
|
return a == b |
|
} |
|
|
|
func (s *splitter) breakInitialism(original string) nameLexem { |
|
return newInitialismNameLexem(original, original) |
|
} |
|
|
|
func (s *splitter) breakCasualString(str []rune) []nameLexem { |
|
segments := make([]nameLexem, 0) |
|
currentSegment := "" |
|
|
|
addCasualNameLexem := func(original string) { |
|
segments = append(segments, newCasualNameLexem(original)) |
|
} |
|
|
|
addInitialismNameLexem := func(original, match string) { |
|
segments = append(segments, newInitialismNameLexem(original, match)) |
|
} |
|
|
|
addNameLexem := func(original string) { |
|
if s.postSplitInitialismCheck { |
|
for _, initialism := range s.initialisms { |
|
if upper(initialism) == upper(original) { |
|
addInitialismNameLexem(original, initialism) |
|
return |
|
} |
|
} |
|
} |
|
|
|
addCasualNameLexem(original) |
|
} |
|
|
|
for _, rn := range string(str) { |
|
if replace, found := nameReplaceTable[rn]; found { |
|
if currentSegment != "" { |
|
addNameLexem(currentSegment) |
|
currentSegment = "" |
|
} |
|
|
|
if replace != "" { |
|
addNameLexem(replace) |
|
} |
|
|
|
continue |
|
} |
|
|
|
if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) { |
|
if currentSegment != "" { |
|
addNameLexem(currentSegment) |
|
currentSegment = "" |
|
} |
|
|
|
continue |
|
} |
|
|
|
if unicode.IsUpper(rn) { |
|
if currentSegment != "" { |
|
addNameLexem(currentSegment) |
|
} |
|
currentSegment = "" |
|
} |
|
|
|
currentSegment += string(rn) |
|
} |
|
|
|
if currentSegment != "" { |
|
addNameLexem(currentSegment) |
|
} |
|
|
|
return segments |
|
}
|
|
|