| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 | // Copyright 2015 The Macaron Authors//// 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 macaronimport (	"regexp"	"strings"	"github.com/Unknwon/com")type patternType int8const (	_PATTERN_STATIC    patternType = iota // /home	_PATTERN_REGEXP                       // /:id([0-9]+)	_PATTERN_PATH_EXT                     // /*.*	_PATTERN_HOLDER                       // /:user	_PATTERN_MATCH_ALL                    // /*)// Leaf represents a leaf route information.type Leaf struct {	parent *Tree	typ        patternType	pattern    string	rawPattern string // Contains wildcard instead of regexp	wildcards  []string	reg        *regexp.Regexp	optional   bool	handle Handle}var wildcardPattern = regexp.MustCompile(`:[a-zA-Z0-9]+`)func isSpecialRegexp(pattern, regStr string, pos []int) bool {	return len(pattern) >= pos[1]+len(regStr) && pattern[pos[1]:pos[1]+len(regStr)] == regStr}// getNextWildcard tries to find next wildcard and update pattern with corresponding regexp.func getNextWildcard(pattern string) (wildcard, _ string) {	pos := wildcardPattern.FindStringIndex(pattern)	if pos == nil {		return "", pattern	}	wildcard = pattern[pos[0]:pos[1]]	// Reach last character or no regexp is given.	if len(pattern) == pos[1] {		return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)	} else if pattern[pos[1]] != '(' {		switch {		case isSpecialRegexp(pattern, ":int", pos):			pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1)		case isSpecialRegexp(pattern, ":string", pos):			pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1)		default:			return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)		}	}	// Cut out placeholder directly.	return wildcard, pattern[:pos[0]] + pattern[pos[1]:]}func getWildcards(pattern string) (string, []string) {	wildcards := make([]string, 0, 2)	// Keep getting next wildcard until nothing is left.	var wildcard string	for {		wildcard, pattern = getNextWildcard(pattern)		if len(wildcard) > 0 {			wildcards = append(wildcards, wildcard)		} else {			break		}	}	return pattern, wildcards}// getRawPattern removes all regexp but keeps wildcards for building URL path.func getRawPattern(rawPattern string) string {	rawPattern = strings.Replace(rawPattern, ":int", "", -1)	rawPattern = strings.Replace(rawPattern, ":string", "", -1)	for {		startIdx := strings.Index(rawPattern, "(")		if startIdx == -1 {			break		}		closeIdx := strings.Index(rawPattern, ")")		if closeIdx > -1 {			rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx+1:]		}	}	return rawPattern}func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) {	pattern = strings.TrimLeft(pattern, "?")	rawPattern = getRawPattern(pattern)	if pattern == "*" {		typ = _PATTERN_MATCH_ALL	} else if pattern == "*.*" {		typ = _PATTERN_PATH_EXT	} else if strings.Contains(pattern, ":") {		typ = _PATTERN_REGEXP		pattern, wildcards = getWildcards(pattern)		if pattern == "(.+)" {			typ = _PATTERN_HOLDER		} else {			reg = regexp.MustCompile(pattern)		}	}	return typ, rawPattern, wildcards, reg}func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf {	typ, rawPattern, wildcards, reg := checkPattern(pattern)	optional := false	if len(pattern) > 0 && pattern[0] == '?' {		optional = true	}	return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle}}// URLPath build path part of URL by given pair values.func (l *Leaf) URLPath(pairs ...string) string {	if len(pairs)%2 != 0 {		panic("number of pairs does not match")	}	urlPath := l.rawPattern	parent := l.parent	for parent != nil {		urlPath = parent.rawPattern + "/" + urlPath		parent = parent.parent	}	for i := 0; i < len(pairs); i += 2 {		if len(pairs[i]) == 0 {			panic("pair value cannot be empty: " + com.ToStr(i))		} else if pairs[i][0] != ':' && pairs[i] != "*" && pairs[i] != "*.*" {			pairs[i] = ":" + pairs[i]		}		urlPath = strings.Replace(urlPath, pairs[i], pairs[i+1], 1)	}	return urlPath}// Tree represents a router tree in Macaron.type Tree struct {	parent *Tree	typ        patternType	pattern    string	rawPattern string	wildcards  []string	reg        *regexp.Regexp	subtrees []*Tree	leaves   []*Leaf}func NewSubtree(parent *Tree, pattern string) *Tree {	typ, rawPattern, wildcards, reg := checkPattern(pattern)	return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)}}func NewTree() *Tree {	return NewSubtree(nil, "")}func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf {	for i := 0; i < len(t.leaves); i++ {		if t.leaves[i].pattern == pattern {			return t.leaves[i]		}	}	leaf := NewLeaf(t, pattern, handle)	// Add exact same leaf to grandparent/parent level without optional.	if leaf.optional {		parent := leaf.parent		if parent.parent != nil {			parent.parent.addLeaf(parent.pattern, handle)		} else {			parent.addLeaf("", handle) // Root tree can add as empty pattern.		}	}	i := 0	for ; i < len(t.leaves); i++ {		if leaf.typ < t.leaves[i].typ {			break		}	}	if i == len(t.leaves) {		t.leaves = append(t.leaves, leaf)	} else {		t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...)	}	return leaf}func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf {	for i := 0; i < len(t.subtrees); i++ {		if t.subtrees[i].pattern == segment {			return t.subtrees[i].addNextSegment(pattern, handle)		}	}	subtree := NewSubtree(t, segment)	i := 0	for ; i < len(t.subtrees); i++ {		if subtree.typ < t.subtrees[i].typ {			break		}	}	if i == len(t.subtrees) {		t.subtrees = append(t.subtrees, subtree)	} else {		t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...)	}	return subtree.addNextSegment(pattern, handle)}func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf {	pattern = strings.TrimPrefix(pattern, "/")	i := strings.Index(pattern, "/")	if i == -1 {		return t.addLeaf(pattern, handle)	}	return t.addSubtree(pattern[:i], pattern[i+1:], handle)}func (t *Tree) Add(pattern string, handle Handle) *Leaf {	pattern = strings.TrimSuffix(pattern, "/")	return t.addNextSegment(pattern, handle)}func (t *Tree) matchLeaf(globLevel int, url string, params Params) (Handle, bool) {	url, err := PathUnescape(url)	if err != nil {		return nil, false	}	for i := 0; i < len(t.leaves); i++ {		switch t.leaves[i].typ {		case _PATTERN_STATIC:			if t.leaves[i].pattern == url {				return t.leaves[i].handle, true			}		case _PATTERN_REGEXP:			results := t.leaves[i].reg.FindStringSubmatch(url)			// Number of results and wildcasrd should be exact same.			if len(results)-1 != len(t.leaves[i].wildcards) {				break			}			for j := 0; j < len(t.leaves[i].wildcards); j++ {				params[t.leaves[i].wildcards[j]] = results[j+1]			}			return t.leaves[i].handle, true		case _PATTERN_PATH_EXT:			j := strings.LastIndex(url, ".")			if j > -1 {				params[":path"] = url[:j]				params[":ext"] = url[j+1:]			} else {				params[":path"] = url			}			return t.leaves[i].handle, true		case _PATTERN_HOLDER:			params[t.leaves[i].wildcards[0]] = url			return t.leaves[i].handle, true		case _PATTERN_MATCH_ALL:			params["*"] = url			params["*"+com.ToStr(globLevel)] = url			return t.leaves[i].handle, true		}	}	return nil, false}func (t *Tree) matchSubtree(globLevel int, segment, url string, params Params) (Handle, bool) {	unescapedSegment, err := PathUnescape(segment)	if err != nil {		return nil, false	}	for i := 0; i < len(t.subtrees); i++ {		switch t.subtrees[i].typ {		case _PATTERN_STATIC:			if t.subtrees[i].pattern == unescapedSegment {				if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {					return handle, true				}			}		case _PATTERN_REGEXP:			results := t.subtrees[i].reg.FindStringSubmatch(unescapedSegment)			if len(results)-1 != len(t.subtrees[i].wildcards) {				break			}			for j := 0; j < len(t.subtrees[i].wildcards); j++ {				params[t.subtrees[i].wildcards[j]] = results[j+1]			}			if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {				return handle, true			}		case _PATTERN_HOLDER:			if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {				params[t.subtrees[i].wildcards[0]] = unescapedSegment				return handle, true			}		case _PATTERN_MATCH_ALL:			if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {				params["*"+com.ToStr(globLevel)] = unescapedSegment				return handle, true			}		}	}	if len(t.leaves) > 0 {		leaf := t.leaves[len(t.leaves)-1]		unescapedURL, err := PathUnescape(segment + "/" + url)		if err != nil {			return nil, false		}		if leaf.typ == _PATTERN_PATH_EXT {			j := strings.LastIndex(unescapedURL, ".")			if j > -1 {				params[":path"] = unescapedURL[:j]				params[":ext"] = unescapedURL[j+1:]			} else {				params[":path"] = unescapedURL			}			return leaf.handle, true		} else if leaf.typ == _PATTERN_MATCH_ALL {			params["*"] = unescapedURL			params["*"+com.ToStr(globLevel)] = unescapedURL			return leaf.handle, true		}	}	return nil, false}func (t *Tree) matchNextSegment(globLevel int, url string, params Params) (Handle, bool) {	i := strings.Index(url, "/")	if i == -1 {		return t.matchLeaf(globLevel, url, params)	}	return t.matchSubtree(globLevel, url[:i], url[i+1:], params)}func (t *Tree) Match(url string) (Handle, Params, bool) {	url = strings.TrimPrefix(url, "/")	url = strings.TrimSuffix(url, "/")	params := make(Params)	handle, ok := t.matchNextSegment(0, url, params)	return handle, params, ok}// MatchTest returns true if given URL is matched by given pattern.func MatchTest(pattern, url string) bool {	t := NewTree()	t.Add(pattern, nil)	_, _, ok := t.Match(url)	return ok}
 |