586 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			586 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2014 beego Author. All Rights Reserved.
 | |
| //
 | |
| // 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 beego
 | |
| 
 | |
| import (
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/astaxie/beego/context"
 | |
| 	"github.com/astaxie/beego/utils"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	allowSuffixExt = []string{".json", ".xml", ".html"}
 | |
| )
 | |
| 
 | |
| // Tree has three elements: FixRouter/wildcard/leaves
 | |
| // fixRouter stores Fixed Router
 | |
| // wildcard stores params
 | |
| // leaves store the endpoint information
 | |
| type Tree struct {
 | |
| 	//prefix set for static router
 | |
| 	prefix string
 | |
| 	//search fix route first
 | |
| 	fixrouters []*Tree
 | |
| 	//if set, failure to match fixrouters search then search wildcard
 | |
| 	wildcard *Tree
 | |
| 	//if set, failure to match wildcard search
 | |
| 	leaves []*leafInfo
 | |
| }
 | |
| 
 | |
| // NewTree return a new Tree
 | |
| func NewTree() *Tree {
 | |
| 	return &Tree{}
 | |
| }
 | |
| 
 | |
| // AddTree will add tree to the exist Tree
 | |
| // prefix should has no params
 | |
| func (t *Tree) AddTree(prefix string, tree *Tree) {
 | |
| 	t.addtree(splitPath(prefix), tree, nil, "")
 | |
| }
 | |
| 
 | |
| func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg string) {
 | |
| 	if len(segments) == 0 {
 | |
| 		panic("prefix should has path")
 | |
| 	}
 | |
| 	seg := segments[0]
 | |
| 	iswild, params, regexpStr := splitSegment(seg)
 | |
| 	// if it's ? meaning can igone this, so add one more rule for it
 | |
| 	if len(params) > 0 && params[0] == ":" {
 | |
| 		params = params[1:]
 | |
| 		if len(segments[1:]) > 0 {
 | |
| 			t.addtree(segments[1:], tree, append(wildcards, params...), reg)
 | |
| 		} else {
 | |
| 			filterTreeWithPrefix(tree, wildcards, reg)
 | |
| 		}
 | |
| 	}
 | |
| 	//Rule: /login/*/access match /login/2009/11/access
 | |
| 	//if already has *, and when loop the access, should as a regexpStr
 | |
| 	if !iswild && utils.InSlice(":splat", wildcards) {
 | |
| 		iswild = true
 | |
| 		regexpStr = seg
 | |
| 	}
 | |
| 	//Rule: /user/:id/*
 | |
| 	if seg == "*" && len(wildcards) > 0 && reg == "" {
 | |
| 		regexpStr = "(.+)"
 | |
| 	}
 | |
| 	if len(segments) == 1 {
 | |
| 		if iswild {
 | |
| 			if regexpStr != "" {
 | |
| 				if reg == "" {
 | |
| 					rr := ""
 | |
| 					for _, w := range wildcards {
 | |
| 						if w == ":splat" {
 | |
| 							rr = rr + "(.+)/"
 | |
| 						} else {
 | |
| 							rr = rr + "([^/]+)/"
 | |
| 						}
 | |
| 					}
 | |
| 					regexpStr = rr + regexpStr
 | |
| 				} else {
 | |
| 					regexpStr = "/" + regexpStr
 | |
| 				}
 | |
| 			} else if reg != "" {
 | |
| 				if seg == "*.*" {
 | |
| 					regexpStr = "([^.]+).(.+)"
 | |
| 				} else {
 | |
| 					for _, w := range params {
 | |
| 						if w == "." || w == ":" {
 | |
| 							continue
 | |
| 						}
 | |
| 						regexpStr = "([^/]+)/" + regexpStr
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			reg = strings.Trim(reg+"/"+regexpStr, "/")
 | |
| 			filterTreeWithPrefix(tree, append(wildcards, params...), reg)
 | |
| 			t.wildcard = tree
 | |
| 		} else {
 | |
| 			reg = strings.Trim(reg+"/"+regexpStr, "/")
 | |
| 			filterTreeWithPrefix(tree, append(wildcards, params...), reg)
 | |
| 			tree.prefix = seg
 | |
| 			t.fixrouters = append(t.fixrouters, tree)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if iswild {
 | |
| 		if t.wildcard == nil {
 | |
| 			t.wildcard = NewTree()
 | |
| 		}
 | |
| 		if regexpStr != "" {
 | |
| 			if reg == "" {
 | |
| 				rr := ""
 | |
| 				for _, w := range wildcards {
 | |
| 					if w == ":splat" {
 | |
| 						rr = rr + "(.+)/"
 | |
| 					} else {
 | |
| 						rr = rr + "([^/]+)/"
 | |
| 					}
 | |
| 				}
 | |
| 				regexpStr = rr + regexpStr
 | |
| 			} else {
 | |
| 				regexpStr = "/" + regexpStr
 | |
| 			}
 | |
| 		} else if reg != "" {
 | |
| 			if seg == "*.*" {
 | |
| 				regexpStr = "([^.]+).(.+)"
 | |
| 				params = params[1:]
 | |
| 			} else {
 | |
| 				for range params {
 | |
| 					regexpStr = "([^/]+)/" + regexpStr
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			if seg == "*.*" {
 | |
| 				params = params[1:]
 | |
| 			}
 | |
| 		}
 | |
| 		reg = strings.TrimRight(strings.TrimRight(reg, "/")+"/"+regexpStr, "/")
 | |
| 		t.wildcard.addtree(segments[1:], tree, append(wildcards, params...), reg)
 | |
| 	} else {
 | |
| 		subTree := NewTree()
 | |
| 		subTree.prefix = seg
 | |
| 		t.fixrouters = append(t.fixrouters, subTree)
 | |
| 		subTree.addtree(segments[1:], tree, append(wildcards, params...), reg)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func filterTreeWithPrefix(t *Tree, wildcards []string, reg string) {
 | |
| 	for _, v := range t.fixrouters {
 | |
| 		filterTreeWithPrefix(v, wildcards, reg)
 | |
| 	}
 | |
| 	if t.wildcard != nil {
 | |
| 		filterTreeWithPrefix(t.wildcard, wildcards, reg)
 | |
| 	}
 | |
| 	for _, l := range t.leaves {
 | |
| 		if reg != "" {
 | |
| 			if l.regexps != nil {
 | |
| 				l.wildcards = append(wildcards, l.wildcards...)
 | |
| 				l.regexps = regexp.MustCompile("^" + reg + "/" + strings.Trim(l.regexps.String(), "^$") + "$")
 | |
| 			} else {
 | |
| 				for _, v := range l.wildcards {
 | |
| 					if v == ":splat" {
 | |
| 						reg = reg + "/(.+)"
 | |
| 					} else {
 | |
| 						reg = reg + "/([^/]+)"
 | |
| 					}
 | |
| 				}
 | |
| 				l.regexps = regexp.MustCompile("^" + reg + "$")
 | |
| 				l.wildcards = append(wildcards, l.wildcards...)
 | |
| 			}
 | |
| 		} else {
 | |
| 			l.wildcards = append(wildcards, l.wildcards...)
 | |
| 			if l.regexps != nil {
 | |
| 				for _, w := range wildcards {
 | |
| 					if w == ":splat" {
 | |
| 						reg = "(.+)/" + reg
 | |
| 					} else {
 | |
| 						reg = "([^/]+)/" + reg
 | |
| 					}
 | |
| 				}
 | |
| 				l.regexps = regexp.MustCompile("^" + reg + strings.Trim(l.regexps.String(), "^$") + "$")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // AddRouter call addseg function
 | |
| func (t *Tree) AddRouter(pattern string, runObject interface{}) {
 | |
| 	t.addseg(splitPath(pattern), runObject, nil, "")
 | |
| }
 | |
| 
 | |
| // "/"
 | |
| // "admin" ->
 | |
| func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, reg string) {
 | |
| 	if len(segments) == 0 {
 | |
| 		if reg != "" {
 | |
| 			t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards, regexps: regexp.MustCompile("^" + reg + "$")})
 | |
| 		} else {
 | |
| 			t.leaves = append(t.leaves, &leafInfo{runObject: route, wildcards: wildcards})
 | |
| 		}
 | |
| 	} else {
 | |
| 		seg := segments[0]
 | |
| 		iswild, params, regexpStr := splitSegment(seg)
 | |
| 		// if it's ? meaning can igone this, so add one more rule for it
 | |
| 		if len(params) > 0 && params[0] == ":" {
 | |
| 			t.addseg(segments[1:], route, wildcards, reg)
 | |
| 			params = params[1:]
 | |
| 		}
 | |
| 		//Rule: /login/*/access match /login/2009/11/access
 | |
| 		//if already has *, and when loop the access, should as a regexpStr
 | |
| 		if !iswild && utils.InSlice(":splat", wildcards) {
 | |
| 			iswild = true
 | |
| 			regexpStr = seg
 | |
| 		}
 | |
| 		//Rule: /user/:id/*
 | |
| 		if seg == "*" && len(wildcards) > 0 && reg == "" {
 | |
| 			regexpStr = "(.+)"
 | |
| 		}
 | |
| 		if iswild {
 | |
| 			if t.wildcard == nil {
 | |
| 				t.wildcard = NewTree()
 | |
| 			}
 | |
| 			if regexpStr != "" {
 | |
| 				if reg == "" {
 | |
| 					rr := ""
 | |
| 					for _, w := range wildcards {
 | |
| 						if w == ":splat" {
 | |
| 							rr = rr + "(.+)/"
 | |
| 						} else {
 | |
| 							rr = rr + "([^/]+)/"
 | |
| 						}
 | |
| 					}
 | |
| 					regexpStr = rr + regexpStr
 | |
| 				} else {
 | |
| 					regexpStr = "/" + regexpStr
 | |
| 				}
 | |
| 			} else if reg != "" {
 | |
| 				if seg == "*.*" {
 | |
| 					regexpStr = "/([^.]+).(.+)"
 | |
| 					params = params[1:]
 | |
| 				} else {
 | |
| 					for range params {
 | |
| 						regexpStr = "/([^/]+)" + regexpStr
 | |
| 					}
 | |
| 				}
 | |
| 			} else {
 | |
| 				if seg == "*.*" {
 | |
| 					params = params[1:]
 | |
| 				}
 | |
| 			}
 | |
| 			t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
 | |
| 		} else {
 | |
| 			var subTree *Tree
 | |
| 			for _, sub := range t.fixrouters {
 | |
| 				if sub.prefix == seg {
 | |
| 					subTree = sub
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if subTree == nil {
 | |
| 				subTree = NewTree()
 | |
| 				subTree.prefix = seg
 | |
| 				t.fixrouters = append(t.fixrouters, subTree)
 | |
| 			}
 | |
| 			subTree.addseg(segments[1:], route, wildcards, reg)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Match router to runObject & params
 | |
| func (t *Tree) Match(pattern string, ctx *context.Context) (runObject interface{}) {
 | |
| 	if len(pattern) == 0 || pattern[0] != '/' {
 | |
| 		return nil
 | |
| 	}
 | |
| 	w := make([]string, 0, 20)
 | |
| 	return t.match(pattern[1:], pattern, w, ctx)
 | |
| }
 | |
| 
 | |
| func (t *Tree) match(treePattern string, pattern string, wildcardValues []string, ctx *context.Context) (runObject interface{}) {
 | |
| 	if len(pattern) > 0 {
 | |
| 		i := 0
 | |
| 		for ; i < len(pattern) && pattern[i] == '/'; i++ {
 | |
| 		}
 | |
| 		pattern = pattern[i:]
 | |
| 	}
 | |
| 	// Handle leaf nodes:
 | |
| 	if len(pattern) == 0 {
 | |
| 		for _, l := range t.leaves {
 | |
| 			if ok := l.match(treePattern, wildcardValues, ctx); ok {
 | |
| 				return l.runObject
 | |
| 			}
 | |
| 		}
 | |
| 		if t.wildcard != nil {
 | |
| 			for _, l := range t.wildcard.leaves {
 | |
| 				if ok := l.match(treePattern, wildcardValues, ctx); ok {
 | |
| 					return l.runObject
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	var seg string
 | |
| 	i, l := 0, len(pattern)
 | |
| 	for ; i < l && pattern[i] != '/'; i++ {
 | |
| 	}
 | |
| 	if i == 0 {
 | |
| 		seg = pattern
 | |
| 		pattern = ""
 | |
| 	} else {
 | |
| 		seg = pattern[:i]
 | |
| 		pattern = pattern[i:]
 | |
| 	}
 | |
| 	for _, subTree := range t.fixrouters {
 | |
| 		if subTree.prefix == seg {
 | |
| 			if len(pattern) != 0 && pattern[0] == '/' {
 | |
| 				treePattern = pattern[1:]
 | |
| 			} else {
 | |
| 				treePattern = pattern
 | |
| 			}
 | |
| 			runObject = subTree.match(treePattern, pattern, wildcardValues, ctx)
 | |
| 			if runObject != nil {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if runObject == nil && len(t.fixrouters) > 0 {
 | |
| 		// Filter the .json .xml .html extension
 | |
| 		for _, str := range allowSuffixExt {
 | |
| 			if strings.HasSuffix(seg, str) {
 | |
| 				for _, subTree := range t.fixrouters {
 | |
| 					if subTree.prefix == seg[:len(seg)-len(str)] {
 | |
| 						runObject = subTree.match(treePattern, pattern, wildcardValues, ctx)
 | |
| 						if runObject != nil {
 | |
| 							ctx.Input.SetParam(":ext", str[1:])
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if runObject == nil && t.wildcard != nil {
 | |
| 		runObject = t.wildcard.match(treePattern, pattern, append(wildcardValues, seg), ctx)
 | |
| 	}
 | |
| 
 | |
| 	if runObject == nil && len(t.leaves) > 0 {
 | |
| 		wildcardValues = append(wildcardValues, seg)
 | |
| 		start, i := 0, 0
 | |
| 		for ; i < len(pattern); i++ {
 | |
| 			if pattern[i] == '/' {
 | |
| 				if i != 0 && start < len(pattern) {
 | |
| 					wildcardValues = append(wildcardValues, pattern[start:i])
 | |
| 				}
 | |
| 				start = i + 1
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 		if start > 0 {
 | |
| 			wildcardValues = append(wildcardValues, pattern[start:i])
 | |
| 		}
 | |
| 		for _, l := range t.leaves {
 | |
| 			if ok := l.match(treePattern, wildcardValues, ctx); ok {
 | |
| 				return l.runObject
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return runObject
 | |
| }
 | |
| 
 | |
| type leafInfo struct {
 | |
| 	// names of wildcards that lead to this leaf. eg, ["id" "name"] for the wildcard ":id" and ":name"
 | |
| 	wildcards []string
 | |
| 
 | |
| 	// if the leaf is regexp
 | |
| 	regexps *regexp.Regexp
 | |
| 
 | |
| 	runObject interface{}
 | |
| }
 | |
| 
 | |
| func (leaf *leafInfo) match(treePattern string, wildcardValues []string, ctx *context.Context) (ok bool) {
 | |
| 	//fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps)
 | |
| 	if leaf.regexps == nil {
 | |
| 		if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path
 | |
| 			return true
 | |
| 		}
 | |
| 		// match *
 | |
| 		if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" {
 | |
| 			ctx.Input.SetParam(":splat", treePattern)
 | |
| 			return true
 | |
| 		}
 | |
| 		// match *.* or :id
 | |
| 		if len(leaf.wildcards) >= 2 && leaf.wildcards[len(leaf.wildcards)-2] == ":path" && leaf.wildcards[len(leaf.wildcards)-1] == ":ext" {
 | |
| 			if len(leaf.wildcards) == 2 {
 | |
| 				lastone := wildcardValues[len(wildcardValues)-1]
 | |
| 				strs := strings.SplitN(lastone, ".", 2)
 | |
| 				if len(strs) == 2 {
 | |
| 					ctx.Input.SetParam(":ext", strs[1])
 | |
| 				}
 | |
| 				ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[:len(wildcardValues)-1]...), strs[0]))
 | |
| 				return true
 | |
| 			} else if len(wildcardValues) < 2 {
 | |
| 				return false
 | |
| 			}
 | |
| 			var index int
 | |
| 			for index = 0; index < len(leaf.wildcards)-2; index++ {
 | |
| 				ctx.Input.SetParam(leaf.wildcards[index], wildcardValues[index])
 | |
| 			}
 | |
| 			lastone := wildcardValues[len(wildcardValues)-1]
 | |
| 			strs := strings.SplitN(lastone, ".", 2)
 | |
| 			if len(strs) == 2 {
 | |
| 				ctx.Input.SetParam(":ext", strs[1])
 | |
| 			}
 | |
| 			if index > (len(wildcardValues) - 1) {
 | |
| 				ctx.Input.SetParam(":path", "")
 | |
| 			} else {
 | |
| 				ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
 | |
| 			}
 | |
| 			return true
 | |
| 		}
 | |
| 		// match :id
 | |
| 		if len(leaf.wildcards) != len(wildcardValues) {
 | |
| 			return false
 | |
| 		}
 | |
| 		for j, v := range leaf.wildcards {
 | |
| 			ctx.Input.SetParam(v, wildcardValues[j])
 | |
| 		}
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	if !leaf.regexps.MatchString(path.Join(wildcardValues...)) {
 | |
| 		return false
 | |
| 	}
 | |
| 	matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
 | |
| 	for i, match := range matches[1:] {
 | |
| 		if i < len(leaf.wildcards) {
 | |
| 			ctx.Input.SetParam(leaf.wildcards[i], match)
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // "/" -> []
 | |
| // "/admin" -> ["admin"]
 | |
| // "/admin/" -> ["admin"]
 | |
| // "/admin/users" -> ["admin", "users"]
 | |
| func splitPath(key string) []string {
 | |
| 	key = strings.Trim(key, "/ ")
 | |
| 	if key == "" {
 | |
| 		return []string{}
 | |
| 	}
 | |
| 	return strings.Split(key, "/")
 | |
| }
 | |
| 
 | |
| // "admin" -> false, nil, ""
 | |
| // ":id" -> true, [:id], ""
 | |
| // "?:id" -> true, [: :id], ""        : meaning can empty
 | |
| // ":id:int" -> true, [:id], ([0-9]+)
 | |
| // ":name:string" -> true, [:name], ([\w]+)
 | |
| // ":id([0-9]+)" -> true, [:id], ([0-9]+)
 | |
| // ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+)
 | |
| // "cms_:id_:page.html" -> true, [:id_ :page], cms_(.+)(.+).html
 | |
| // "cms_:id(.+)_:page.html" -> true, [:id :page], cms_(.+)_(.+).html
 | |
| // "*" -> true, [:splat], ""
 | |
| // "*.*" -> true,[. :path :ext], ""      . meaning separator
 | |
| func splitSegment(key string) (bool, []string, string) {
 | |
| 	if strings.HasPrefix(key, "*") {
 | |
| 		if key == "*.*" {
 | |
| 			return true, []string{".", ":path", ":ext"}, ""
 | |
| 		}
 | |
| 		return true, []string{":splat"}, ""
 | |
| 	}
 | |
| 	if strings.ContainsAny(key, ":") {
 | |
| 		var paramsNum int
 | |
| 		var out []rune
 | |
| 		var start bool
 | |
| 		var startexp bool
 | |
| 		var param []rune
 | |
| 		var expt []rune
 | |
| 		var skipnum int
 | |
| 		params := []string{}
 | |
| 		reg := regexp.MustCompile(`[a-zA-Z0-9_]+`)
 | |
| 		for i, v := range key {
 | |
| 			if skipnum > 0 {
 | |
| 				skipnum--
 | |
| 				continue
 | |
| 			}
 | |
| 			if start {
 | |
| 				//:id:int and :name:string
 | |
| 				if v == ':' {
 | |
| 					if len(key) >= i+4 {
 | |
| 						if key[i+1:i+4] == "int" {
 | |
| 							out = append(out, []rune("([0-9]+)")...)
 | |
| 							params = append(params, ":"+string(param))
 | |
| 							start = false
 | |
| 							startexp = false
 | |
| 							skipnum = 3
 | |
| 							param = make([]rune, 0)
 | |
| 							paramsNum++
 | |
| 							continue
 | |
| 						}
 | |
| 					}
 | |
| 					if len(key) >= i+7 {
 | |
| 						if key[i+1:i+7] == "string" {
 | |
| 							out = append(out, []rune(`([\w]+)`)...)
 | |
| 							params = append(params, ":"+string(param))
 | |
| 							paramsNum++
 | |
| 							start = false
 | |
| 							startexp = false
 | |
| 							skipnum = 6
 | |
| 							param = make([]rune, 0)
 | |
| 							continue
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				// params only support a-zA-Z0-9
 | |
| 				if reg.MatchString(string(v)) {
 | |
| 					param = append(param, v)
 | |
| 					continue
 | |
| 				}
 | |
| 				if v != '(' {
 | |
| 					out = append(out, []rune(`(.+)`)...)
 | |
| 					params = append(params, ":"+string(param))
 | |
| 					param = make([]rune, 0)
 | |
| 					paramsNum++
 | |
| 					start = false
 | |
| 					startexp = false
 | |
| 				}
 | |
| 			}
 | |
| 			if startexp {
 | |
| 				if v != ')' {
 | |
| 					expt = append(expt, v)
 | |
| 					continue
 | |
| 				}
 | |
| 			}
 | |
| 			// Escape Sequence '\'
 | |
| 			if i > 0 && key[i-1] == '\\' {
 | |
| 				out = append(out, v)
 | |
| 			} else if v == ':' {
 | |
| 				param = make([]rune, 0)
 | |
| 				start = true
 | |
| 			} else if v == '(' {
 | |
| 				startexp = true
 | |
| 				start = false
 | |
| 				if len(param) > 0 {
 | |
| 					params = append(params, ":"+string(param))
 | |
| 					param = make([]rune, 0)
 | |
| 				}
 | |
| 				paramsNum++
 | |
| 				expt = make([]rune, 0)
 | |
| 				expt = append(expt, '(')
 | |
| 			} else if v == ')' {
 | |
| 				startexp = false
 | |
| 				expt = append(expt, ')')
 | |
| 				out = append(out, expt...)
 | |
| 				param = make([]rune, 0)
 | |
| 			} else if v == '?' {
 | |
| 				params = append(params, ":")
 | |
| 			} else {
 | |
| 				out = append(out, v)
 | |
| 			}
 | |
| 		}
 | |
| 		if len(param) > 0 {
 | |
| 			if paramsNum > 0 {
 | |
| 				out = append(out, []rune(`(.+)`)...)
 | |
| 			}
 | |
| 			params = append(params, ":"+string(param))
 | |
| 		}
 | |
| 		return true, params, string(out)
 | |
| 	}
 | |
| 	return false, nil, ""
 | |
| }
 |