Преглед изворни кода

抽取逻辑增加标签库、kv、正则抽取

zhangjinkun пре 6 година
родитељ
комит
a804805c81
100 измењених фајлова са 15916 додато и 337 уклоњено
  1. 266 309
      src/jy/extract/extract.go
  2. 256 0
      src/jy/extract/extractInit.go
  3. 3 0
      src/jy/pretreated/README.MD
  4. 469 0
      src/jy/pretreated/analykv.go
  5. 149 0
      src/jy/pretreated/analystep.go
  6. 2168 0
      src/jy/pretreated/analytable.go
  7. 112 0
      src/jy/pretreated/bys_test.go
  8. 782 0
      src/jy/pretreated/colonkv.go
  9. 767 0
      src/jy/pretreated/division.go
  10. 26 0
      src/jy/pretreated/kv_test.go
  11. 142 0
      src/jy/pretreated/multipackage.go
  12. 78 0
      src/jy/pretreated/spacekv.go
  13. 94 0
      src/jy/pretreated/tablev1_test.go
  14. 777 0
      src/jy/pretreated/tablev2.go
  15. 385 0
      src/jy/pretreated/winnerorder.go
  16. 87 0
      src/jy/util/article.go
  17. 104 0
      src/jy/util/config.go
  18. 72 11
      src/jy/util/script.go
  19. 20 0
      src/jy/util/tablek.go
  20. 148 0
      src/jy/util/tagmatch.go
  21. 3 12
      src/jy/util/util.go
  22. 261 0
      src/jy/util/util2.go
  23. 4 5
      src/main_test.go
  24. 187 0
      src/res/formattext.json
  25. 212 0
      src/res/tablek.json
  26. 68 0
      src/res/tablev1.json
  27. 24 0
      src/web/res/bower_components/select2/.bower.json
  28. 6 0
      src/web/res/bower_components/select2/.editorconfig
  29. 203 0
      src/web/res/bower_components/select2/.github/CONTRIBUTING.md
  30. 20 0
      src/web/res/bower_components/select2/.github/ISSUE_TEMPLATE.md
  31. 13 0
      src/web/res/bower_components/select2/.github/PULL_REQUEST_TEMPLATE.md
  32. 3 0
      src/web/res/bower_components/select2/.gitignore
  33. 4 0
      src/web/res/bower_components/select2/.jshintignore
  34. 25 0
      src/web/res/bower_components/select2/.jshintrc
  35. 22 0
      src/web/res/bower_components/select2/.travis.yml
  36. 272 0
      src/web/res/bower_components/select2/CHANGELOG.md
  37. 380 0
      src/web/res/bower_components/select2/Gruntfile.js
  38. 21 0
      src/web/res/bower_components/select2/LICENSE.md
  39. 123 0
      src/web/res/bower_components/select2/README.md
  40. 13 0
      src/web/res/bower_components/select2/bower.json
  41. 19 0
      src/web/res/bower_components/select2/component.json
  42. 25 0
      src/web/res/bower_components/select2/composer.json
  43. 484 0
      src/web/res/bower_components/select2/dist/css/select2.css
  44. 0 0
      src/web/res/bower_components/select2/dist/css/select2.min.css
  45. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/af.js
  46. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ar.js
  47. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/az.js
  48. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/bg.js
  49. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/bs.js
  50. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ca.js
  51. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/cs.js
  52. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/da.js
  53. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/de.js
  54. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/dsb.js
  55. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/el.js
  56. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/en.js
  57. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/es.js
  58. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/et.js
  59. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/eu.js
  60. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/fa.js
  61. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/fi.js
  62. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/fr.js
  63. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/gl.js
  64. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/he.js
  65. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/hi.js
  66. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/hr.js
  67. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/hsb.js
  68. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/hu.js
  69. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/hy.js
  70. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/id.js
  71. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/is.js
  72. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/it.js
  73. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ja.js
  74. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/km.js
  75. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ko.js
  76. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/lt.js
  77. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/lv.js
  78. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/mk.js
  79. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ms.js
  80. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/nb.js
  81. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/nl.js
  82. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/pl.js
  83. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ps.js
  84. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/pt-BR.js
  85. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/pt.js
  86. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ro.js
  87. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/ru.js
  88. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/sk.js
  89. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/sl.js
  90. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/sr-Cyrl.js
  91. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/sr.js
  92. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/sv.js
  93. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/th.js
  94. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/tr.js
  95. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/uk.js
  96. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/vi.js
  97. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/zh-CN.js
  98. 3 0
      src/web/res/bower_components/select2/dist/js/i18n/zh-TW.js
  99. 6457 0
      src/web/res/bower_components/select2/dist/js/select2.full.js
  100. 0 0
      src/web/res/bower_components/select2/dist/js/select2.full.min.js

+ 266 - 309
src/jy/extract/extract.go

@@ -3,10 +3,10 @@ package extract
 import (
 	"encoding/json"
 	db "jy/mongodbutil"
+	"jy/pretreated"
 	ju "jy/util"
 	"log"
 	qu "qfw/util"
-	"regexp"
 	"strings"
 	"sync"
 	"time"
@@ -14,62 +14,16 @@ import (
 	"gopkg.in/mgo.v2/bson"
 )
 
-type RegLuaInfo struct { //正则或脚本信息
-	Code, Name, Field string
-	RuleText          string
-	IsLua             bool
-	RegPreBac         *ExtReg
-	RegCore           *ExtReg
-}
-type ExtReg struct {
-	Reg        *regexp.Regexp
-	Replace    string
-	Bextract   bool
-	ExtractPos map[string]int
-}
-type RuleCore struct {
-	LuaLogic  string        //进入逻辑
-	RulePres  []*RegLuaInfo //前置规则
-	RuleBacks []*RegLuaInfo //后置规则
-	RuleCores []*RegLuaInfo //抽取规则
-}
-type TaskInfo struct {
-	Name, Version, TrackColl     string    //名称、版本、追踪记录表
-	FromDbAddr, FromDB, FromColl string    //抽取数据库地址、库名、表名
-	SaveColl, LastExtId          string    //抽取结果表、上次抽取信息id
-	DB                           *db.Pool  //数据库连接池
-	IsEtxLog                     bool      //是否开启抽取日志
-	ProcessPool                  chan bool //任务进程池
-}
-type ExtField struct {
-	Field string        //属性
-	Value []interface{} //抽取结果
-}
-type ExtractTask struct {
-	Id        string        //任务id
-	IsRun     bool          //是否启动
-	Content   string        //信息内容
-	TaskInfo  *TaskInfo     //任务信息
-	RulePres  []*RegLuaInfo //前置规则
-	RuleBacks []*RegLuaInfo //后置规则
-	RuleCores []*RuleCore   //抽取规则
-}
-
 var (
-	lock     sync.RWMutex
-	cut      = ju.NewCut()                          //获取正文并清理
-	ExtLogs  map[*TaskInfo][]map[string]interface{} //抽取日志
-	TaskList map[string]*ExtractTask                //任务列表
+	lock      sync.RWMutex
+	cut       = ju.NewCut()                          //获取正文并清理
+	ExtLogs   map[*TaskInfo][]map[string]interface{} //抽取日志
+	TaskList  map[string]*ExtractTask                //任务列表
+	saveLimit = 200                                  //抽取日志批量保存
 
-	saveLimit = 200                     //抽取日志批量保存
-	nfields   = []string{"contenthtml"} //日志保存排除字段
+	Fields = `{"title":1,"detail":1,"contenthtml":1,"href":1,"spidercode":1:"toptype":1,"area":1,"city":1}`
 )
 
-func init() {
-	TaskList = make(map[string]*ExtractTask)
-	go SaveExtLog()
-}
-
 //启动抽取
 func StartExtractTaskId(taskId string) bool {
 	ext := TaskList[taskId]
@@ -82,6 +36,7 @@ func StartExtractTaskId(taskId string) bool {
 		ext.InitRulePres()
 		ext.InitRuleBacks()
 		ext.InitRuleCore()
+		ext.InitTag()
 		//只启动一次taskId
 		go RunExtractTask(ext)
 	}
@@ -105,22 +60,21 @@ func RunExtractTask(ext *ExtractTask) {
 	if !ext.IsRun {
 		return
 	}
-	var fields = `{"title":1,"detail":1,"contenthtml":1}`
 	query := bson.M{"_id": bson.M{"$gt": bson.ObjectIdHex(ext.TaskInfo.LastExtId)}}
-	list, _ := ext.TaskInfo.DB.Find(ext.TaskInfo.FromColl, query, nil, fields, false, -1, -1)
+	list, _ := ext.TaskInfo.DB.Find(ext.TaskInfo.FromColl, query, nil, Fields, false, -1, -1)
 	for _, v := range *list {
 		if !ext.IsRun {
 			break
 		}
-		v = PreInfo(v)
+		j := PreInfo(v)
 		ext.TaskInfo.ProcessPool <- true
-		go ext.ExtractProcess(v)
+		go ext.ExtractProcess(j)
 	}
 	time.AfterFunc(30*time.Minute, func() { RunExtractTask(ext) })
 }
 
 //信息预处理
-func PreInfo(doc map[string]interface{}) map[string]interface{} {
+func PreInfo(doc map[string]interface{}) *ju.Job {
 	detail := ""
 	d1 := doc["detail"].(string)
 	d2 := doc["contenthtml"].(string)
@@ -132,237 +86,82 @@ func PreInfo(doc map[string]interface{}) map[string]interface{} {
 	detail = ju.CutLableStr(detail)
 	detail = cut.ClearHtml(detail)
 	doc["detail"] = detail
-	delete(doc, "contenthtml")
-	return doc
-}
-
-//加载任务信息
-func (e *ExtractTask) InitTaskInfo() {
-	task, _ := db.Mgo.FindById("task", e.Id, nil)
-	if len(*task) > 1 {
-		e.TaskInfo = &TaskInfo{
-			Name:        (*task)["s_taskname"].(string),
-			Version:     (*task)["s_version"].(string),
-			TrackColl:   (*task)["s_trackcoll"].(string),
-			FromDbAddr:  (*task)["s_mgoaddr"].(string),
-			FromDB:      (*task)["s_mgodb"].(string),
-			FromColl:    (*task)["s_mgocoll"].(string),
-			SaveColl:    (*task)["s_mgosavecoll"].(string),
-			IsEtxLog:    qu.If(qu.IntAll((*task)["i_track"]) == 1, true, false).(bool),
-			LastExtId:   qu.ObjToString((*task)["s_extlastid"]),
-			ProcessPool: make(chan bool, qu.IntAllDef((*task)["i_process"], 1)),
-		}
-	} else {
-		return
+	href := qu.ObjToString(doc["href"])
+	if strings.HasPrefix(href, "http://") {
+		href = href[7:]
+	} else if strings.HasPrefix(href, "https://") {
+		href = href[8:]
 	}
-}
-
-//加载前置规则
-func (e *ExtractTask) InitRulePres() {
-	defer qu.Catch()
-	list, _ := db.Mgo.Find("rule_pre", `{"s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
-	for _, v := range *list {
-		rinfo := &RegLuaInfo{
-			Code:  v["s_code"].(string),
-			Name:  v["s_name"].(string),
-			IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
-		}
-		if rinfo.IsLua {
-			rinfo.RuleText = v["s_luascript"].(string)
-		} else {
-			rinfo.RuleText = v["s_rule"].(string)
-			tmp := strings.Split(rinfo.RuleText, "__")
-			if len(tmp) == 2 {
-				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
-			} else {
-				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
-			}
-		}
-		e.RulePres = append(e.RulePres, rinfo)
+	pos := strings.Index(href, "/")
+	if pos > 0 {
+		href = href[:pos]
 	}
-}
-
-//加载后置规则
-func (e *ExtractTask) InitRuleBacks() {
-	defer qu.Catch()
-	list, _ := db.Mgo.Find("rule_back", `{"s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
-	for _, v := range *list {
-		rinfo := &RegLuaInfo{
-			Code:  v["s_code"].(string),
-			Name:  v["s_name"].(string),
-			IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
-		}
-		if rinfo.IsLua {
-			rinfo.RuleText = v["s_luascript"].(string)
-		} else {
-			rinfo.RuleText = v["s_rule"].(string)
-			tmp := strings.Split(rinfo.RuleText, "__")
-			if len(tmp) == 2 {
-				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
-			} else {
-				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
-			}
-		}
-		e.RuleBacks = append(e.RuleBacks, rinfo)
+	doc["domain"] = href
+	toptype := qu.ObjToString(doc["toptype"])
+	if qu.ObjToString(doc["type"]) == "bid" {
+		toptype = "结果"
 	}
-}
-
-//加载抽取规则
-func (e *ExtractTask) InitRuleCore() {
-	defer qu.Catch()
-	list, _ := db.Mgo.Find("rule_logic", `{"s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
-	for _, vv := range *list {
-		if b, _ := vv["isuse"].(bool); !b {
-			continue
-		}
-		rcore := &RuleCore{}
-		//是否进入逻辑脚本
-		rcore.LuaLogic = qu.ObjToString(vv["s_luascript"])
-		//前置规则
-		rulePres := []*RegLuaInfo{}
-		plist, _ := db.Mgo.Find("rule_logicpre", `{"sid":"`+qu.BsonIdToSId(vv["_id"])+`","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
-		for _, v := range *plist {
-			rinfo := &RegLuaInfo{
-				Code:  v["s_code"].(string),
-				Name:  v["s_name"].(string),
-				IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
-			}
-			if rinfo.IsLua {
-				rinfo.RuleText = v["s_luascript"].(string)
-			} else {
-				rinfo.RuleText = v["s_rule"].(string)
-				rinfo.Field = v["s_field"].(string)
-				tmp := strings.Split(rinfo.RuleText, "__")
-				if len(tmp) == 2 {
-					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
-				} else {
-					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
-				}
-			}
-			rulePres = append(rulePres, rinfo)
-		}
-		rcore.RulePres = rulePres
-
-		//后置规则
-		ruleBacks := []*RegLuaInfo{}
-		blist, _ := db.Mgo.Find("rule_logicback", `{"sid":"`+qu.BsonIdToSId(vv["_id"])+`","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
-		for _, v := range *blist {
-			rinfo := &RegLuaInfo{
-				Code:  v["s_code"].(string),
-				Name:  v["s_name"].(string),
-				IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
-			}
-			if rinfo.IsLua {
-				rinfo.RuleText = v["s_luascript"].(string)
-			} else {
-				rinfo.RuleText = v["s_rule"].(string)
-				rinfo.Field = v["s_field"].(string)
-				tmp := strings.Split(rinfo.RuleText, "__")
-				if len(tmp) == 2 {
-					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
-				} else {
-					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
-				}
-			}
-			ruleBacks = append(ruleBacks, rinfo)
-		}
-		rcore.RuleBacks = ruleBacks
-
-		//抽取规则
-		ruleCores := []*RegLuaInfo{}
-		clist, _ := db.Mgo.Find("rule_logicore", `{"sid":"`+qu.BsonIdToSId(vv["_id"])+`","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
-		for _, v := range *clist {
-			if b, _ := v["isuse"].(bool); !b {
-				continue
-			}
-			rinfo := &RegLuaInfo{
-				Code:  v["s_code"].(string),
-				Name:  v["s_name"].(string),
-				IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
-			}
-			if rinfo.IsLua {
-				rinfo.RuleText = v["s_luascript"].(string)
-			} else {
-				rinfo.RuleText = v["s_rule"].(string)
-				rinfo.Field = v["s_field"].(string)
-				tmp := strings.Split(rinfo.RuleText, "__")
-				if len(tmp) == 2 {
-					epos := strings.Split(tmp[1], ",")
-					posm := map[string]int{}
-					for _, v := range epos {
-						ks := strings.Split(v, ":")
-						if len(ks) == 2 { //(.*)招标公告(.*)__2:projectname,4:area
-							posm[ks[1]] = qu.IntAll(ks[0])
-						} else { //(.*)招标公告__2
-							posm[rinfo.Field] = qu.IntAll(ks[0])
-						}
-					}
-					rinfo.RegCore = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Bextract: true, ExtractPos: posm}
-				} else {
-					rinfo.RegCore = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Bextract: false}
-				}
-			}
-			ruleCores = append(ruleCores, rinfo)
-		}
-		rcore.RuleCores = ruleCores
-
-		//
-		e.RuleCores = append(e.RuleCores, rcore)
+	if toptype == "" {
+		toptype = "*"
+	}
+	j := &ju.Job{
+		SourceMid:  qu.BsonIdToSId(doc["_id"]),
+		Category:   toptype,
+		Content:    qu.ObjToString(doc["detail"]),
+		SpiderCode: qu.ObjToString(doc["spidercode"]),
+		Domain:     qu.ObjToString(doc["domain"]),
+		Href:       qu.ObjToString(doc["href"]),
+		Title:      qu.ObjToString(doc["title"]),
+		Data:       &doc,
+		City:       qu.ObjToString(doc["city"]),
+		Province:   qu.ObjToString(doc["area"]),
+		Result:     map[string][]*ju.ExtField{},
 	}
+	pretreated.AnalyStart(j)
+	return j
 }
 
 //抽取
-func (e *ExtractTask) ExtractProcess(doc map[string]interface{}) {
+func (e *ExtractTask) ExtractProcess(j *ju.Job) {
 	qu.Catch()
 	qu.Try(func() {
-		result := map[string]*ExtField{} //抽取结果
+		doc := *j.Data
 		//前置规则,结果覆盖doc属性
 		for _, v := range e.RulePres {
-			doc = ExtRegPre(doc, v, e.TaskInfo)
+			doc = ExtRegPre(doc, j, v, e.TaskInfo)
 		}
 		//log.Println("全局前置规则", doc)
+
 		//抽取规则
 		for _, vc := range e.RuleCores {
-			tmp := ju.DeepCopy(doc, []string{}).(map[string]interface{})
+			tmp := ju.DeepCopy(doc).(map[string]interface{})
 			//是否进入逻辑
 			if !ju.Logic(vc.LuaLogic, tmp) {
 				continue
 			}
 			//抽取-前置规则
 			for _, v := range vc.RulePres {
-				tmp = ExtRegPre(tmp, v, e.TaskInfo)
+				tmp = ExtRegPre(tmp, j, v, e.TaskInfo)
 			}
 			//log.Println("抽取-前置规则", tmp)
 
 			//抽取-规则
 			for _, v := range vc.RuleCores {
-				tmp = ExtRegCore(tmp, v, e.TaskInfo)
+				ExtRegCore(tmp, j, v, e)
 			}
 			//log.Println("抽取-规则", tmp)
 
 			//抽取-后置规则
 			for _, v := range vc.RuleBacks {
-				tmp = ExtRegBack(tmp, v, e.TaskInfo)
+				ExtRegBack(j, v, e.TaskInfo)
 			}
 			//log.Println("抽取-后置规则", tmp)
+
 			//全局后置规则
 			for _, v := range e.RuleBacks {
-				tmp = ExtRegBack(tmp, v, e.TaskInfo)
+				ExtRegBack(j, v, e.TaskInfo)
 			}
-			//log.Println("全局后置规则", tmp)
-
-			//抽取结果赋值
-			for k, v := range tmp {
-				if k == "_id" || k == "detail" || k == "contenthtml" {
-					continue
-				}
-				if result[k] == nil {
-					result[k] = &ExtField{Field: k, Value: []interface{}{v}}
-				} else {
-					result[k].Value = append(result[k].Value, v)
-				}
-			}
-			bs, _ := json.Marshal(result)
+			bs, _ := json.Marshal(j.Result)
 			log.Println("抽取结果", string(bs))
 		}
 		//抽取结果保存 todo
@@ -375,93 +174,252 @@ func (e *ExtractTask) ExtractProcess(doc map[string]interface{}) {
 }
 
 //前置过滤
-func ExtRegPre(doc map[string]interface{}, v *RegLuaInfo, t *TaskInfo) map[string]interface{} {
-	before := ju.DeepCopy(doc, []string{}).(map[string]interface{})
+func ExtRegPre(doc map[string]interface{}, j *ju.Job, in *RegLuaInfo, t *TaskInfo) map[string]interface{} {
+	before := ju.DeepCopy(doc).(map[string]interface{})
 	extinfo := map[string]interface{}{}
-	if v.IsLua {
-		lua := ju.LuaScript{Code: v.Code, Name: v.Name, Doc: doc, Script: v.RuleText}
-		extinfo = lua.RunScript()
+	if in.IsLua {
+		lua := ju.LuaScript{Code: in.Code, Name: in.Name, Doc: doc, Script: in.RuleText}
+		if j != nil {
+			lua.Block = j.Block
+		}
+		extinfo = lua.RunScript("pre")
 		for k, v := range extinfo { //结果覆盖原doc
 			doc[k] = v
 		}
-		AddExtLog(before, extinfo, v, t) //抽取日志
+		AddExtLog(j.SourceMid, before, extinfo, in, t) //抽取日志
 	} else {
-		key := qu.If(v.Field == "", "detail", v.Field).(string)
+		key := qu.If(in.Field == "", "detail", in.Field).(string)
 		text := qu.ObjToString(doc[key])
-		extinfo[key] = v.RegPreBac.Reg.ReplaceAllString(text, "")
-		doc[key] = extinfo[key]          //结果覆盖原doc
-		AddExtLog(before, extinfo, v, t) //抽取日志
+		extinfo[key] = in.RegPreBac.Reg.ReplaceAllString(text, "")
+		doc[key] = extinfo[key]                        //结果覆盖原doc
+		AddExtLog(j.SourceMid, before, extinfo, in, t) //抽取日志
 	}
 	return doc
 }
 
 //抽取-规则
-func ExtRegCore(doc map[string]interface{}, v *RegLuaInfo, t *TaskInfo) map[string]interface{} {
-	before := ju.DeepCopy(doc, nfields).(map[string]interface{})
-	extinfo := map[string]interface{}{}
-	if v.IsLua {
-		lua := ju.LuaScript{Code: v.Code, Name: v.Name, Doc: doc, Script: v.RuleText}
-		extinfo = lua.RunScript()
+func ExtRegCore(doc map[string]interface{}, j *ju.Job, in *RegLuaInfo, et *ExtractTask) {
+	if in.IsLua {
+		lua := ju.LuaScript{Code: in.Code, Name: in.Name, Doc: doc, Script: in.RuleText}
+		if in.IsHasFields { //lua脚本配置有属性字段
+			lua.KvMap = getKvByLuaFields(j, in, et.Tag)
+		}
+		lua.Block = j.Block
+		extinfo := lua.RunScript("core")
 		for k, v := range extinfo {
-			doc[k] = v //结果覆盖原doc
+			if j.Result[k] == nil {
+				j.Result[k] = [](*ju.ExtField){}
+			}
+			if tmps, ok := v.([]map[string]interface{}); ok {
+				for _, tmp := range tmps {
+					j.Result[k] = append(j.Result[k], &ju.ExtField{k, qu.ObjToString(tmp["key"]), qu.ObjToString(tmp["type"]), tmp["value"]})
+				}
+			}
 		}
-		AddExtLog(before, extinfo, v, t) //抽取日志
+		AddExtLog(j.SourceMid, nil, extinfo, in, et.TaskInfo) //抽取日志
 	} else {
-		if v.Field == "" {
-			return doc
+		if in.Field != "" {
+			//全文正则
+			text := qu.ObjToString(doc["detail"])
+			extinfo := extRegCoreToResult(text, j, in)
+			AddExtLog(j.SourceMid, nil, extinfo, in, et.TaskInfo) //抽取日志
 		}
-		text := qu.ObjToString(doc["detail"])
-		if v.RegCore.Bextract { //正则是两部分的,可以直接抽取的(含下划线)
-			apos := v.RegCore.Reg.FindAllStringSubmatchIndex(text, -1)
-			if len(apos) > 0 {
-				pos := apos[0]
-				for k, p := range v.RegCore.ExtractPos {
-					if len(pos) > p {
-						extinfo[k] = text[pos[p]:pos[p+1]]
-						doc[k] = extinfo[k] //结果覆盖原doc
+	}
+}
+
+//lua脚本根据属性设置提取kv值
+func getKvByLuaFields(j *ju.Job, in *RegLuaInfo, t map[string][]*Tag) map[string][]map[string]interface{} {
+	kvmap := map[string][]map[string]interface{}{}
+	for _, vv := range in.LFields {
+		field := qu.ObjToString(vv)
+		tags := t[qu.ObjToString(vv)] //获取对应标签库
+		for _, bl := range j.Block {
+			//冒号kv
+			if bl.ColonKV != nil {
+				kvs := bl.ColonKV.Kvs
+				kvs2 := bl.ColonKV.Kvs_2
+				for _, tag := range tags {
+					for _, kv := range kvs {
+						if kv.Key == tag.Key {
+							text := ju.TrimLRSpace(kv.Value, "")
+							if text != "" {
+								kvmap[field] = append(kvmap[field], map[string]interface{}{
+									"value": text,
+									"type":  "colon1",
+									"field": field,
+									"key":   tag.Key,
+								})
+							}
+							break
+						}
+					}
+					for _, kv := range kvs2 {
+						if kv.Key == tag.Key {
+							text := ju.TrimLRSpace(kv.Value, "")
+							if text != "" {
+								kvmap[field] = append(kvmap[field], map[string]interface{}{
+									"value": text,
+									"type":  "colon2",
+									"field": field,
+									"key":   tag.Key,
+								})
+							}
+							break
+						}
+					}
+				}
+			}
+			//空格kv
+			if bl.SpaceKV != nil {
+				kvs := bl.SpaceKV.Kvs
+				for _, tag := range tags {
+					for _, kv := range kvs {
+						if kv.Key == tag.Key {
+							text := ju.TrimLRSpace(kv.Value, "")
+							if text != "" {
+								kvmap[field] = append(kvmap[field], map[string]interface{}{
+									"value": text,
+									"type":  "space",
+									"field": field,
+									"key":   tag.Key,
+								})
+							}
+							break
+						}
+					}
+				}
+			}
+			//表格kv
+			if bl.TableKV != nil {
+				kv := bl.TableKV.Kv
+				for _, tag := range tags {
+					for k, val := range kv {
+						if k == tag.Key {
+							text := ju.TrimLRSpace(val, "")
+							if text != "" {
+								kvmap[field] = append(kvmap[field], map[string]interface{}{
+									"value": text,
+									"type":  "table",
+									"field": field,
+									"key":   tag.Key,
+								})
+							}
+							break
+						}
 					}
 				}
 			}
-		} else {
-			extinfo[v.Field] = v.RegCore.Reg.ReplaceAllString(text, "")
-			doc[v.Field] = extinfo[v.Field] //结果覆盖原doc
 		}
-		AddExtLog(before, extinfo, v, t) //抽取日志
 	}
-	return doc
+	return kvmap
 }
 
-//后置过滤
-func ExtRegBack(doc map[string]interface{}, v *RegLuaInfo, t *TaskInfo) map[string]interface{} {
-	before := ju.DeepCopy(doc, nfields).(map[string]interface{})
+//正则提取结果
+func extRegCoreToResult(text string, j *ju.Job, v *RegLuaInfo) map[string]interface{} {
 	extinfo := map[string]interface{}{}
-	if v.IsLua {
-		lua := ju.LuaScript{Code: v.Code, Name: v.Name, Doc: doc, Script: v.RuleText}
-		extinfo = lua.RunScript()
-		for k, v := range extinfo { //结果覆盖原doc
-			doc[k] = v
+	if v.RegCore.Bextract { //正则是两部分的,可以直接抽取的(含下划线)
+		apos := v.RegCore.Reg.FindAllStringSubmatchIndex(text, -1)
+		if len(apos) > 0 {
+			pos := apos[0]
+			for k, p := range v.RegCore.ExtractPos {
+				if len(pos) > p {
+					val := text[pos[p]:pos[p+1]]
+					extinfo[k] = val
+					if val != "" {
+						if j.Result[v.Field] == nil {
+							j.Result[k] = [](*ju.ExtField){}
+						}
+						j.Result[k] = append(j.Result[k], &ju.ExtField{k, v.RuleText, "regexp", val})
+					}
+				}
+			}
 		}
-		AddExtLog(before, extinfo, v, t) //抽取日志
 	} else {
-		if v.Field != "" && qu.ObjToString(doc[v.Field]) != "" {
-			extinfo[v.Field] = v.RegPreBac.Reg.ReplaceAllString(qu.ObjToString(doc[v.Field]), v.RegPreBac.Replace)
-			doc[v.Field] = extinfo[v.Field]
+		val := v.RegCore.Reg.ReplaceAllString(text, "")
+		extinfo[v.Field] = val
+		if val != "" {
+			if j.Result[v.Field] == nil {
+				j.Result[v.Field] = [](*ju.ExtField){}
+			}
+			j.Result[v.Field] = append(j.Result[v.Field], &ju.ExtField{v.Field, v.RuleText, "regexp", val})
+		}
+	}
+	return extinfo
+}
+
+//后置过滤
+func ExtRegBack(j *ju.Job, in *RegLuaInfo, t *TaskInfo) {
+	if in.IsLua {
+		result := getResultMapForLua(j)
+		lua := ju.LuaScript{Code: in.Code, Name: in.Name, Result: result, Script: in.RuleText}
+		if j != nil {
+			lua.Block = j.Block
+		}
+		extinfo := lua.RunScript("back")
+		for k, v := range extinfo {
+			if tmps, ok := v.([]map[string]interface{}); ok {
+				j.Result[k] = [](*ju.ExtField){}
+				for _, tmp := range tmps {
+					j.Result[k] = append(j.Result[k], &ju.ExtField{k, qu.ObjToString(tmp["key"]), qu.ObjToString(tmp["type"]), tmp["value"]})
+				}
+			}
+		}
+		AddExtLog(j.SourceMid, result, extinfo, in, t) //抽取日志
+	} else {
+		extinfo := map[string]interface{}{}
+		if in.Field != "" && j.Result[in.Field] != nil {
+			tmp := j.Result[in.Field]
+			exts := []interface{}{}
+			for k, v := range tmp {
+				text := qu.ObjToString(v.Value)
+				if text != "" {
+					text = in.RegPreBac.Reg.ReplaceAllString(text, in.RegPreBac.Replace)
+				}
+				j.Result[in.Field][k].Value = text
+				exts = append(exts, text)
+			}
+			extinfo[in.Field] = exts
+			AddExtLog(j.SourceMid, tmp, extinfo, in, t) //抽取日志
 		} else {
-			for k, val := range doc {
-				if k == "_id" || qu.ObjToString(val) == "" {
-					continue
+			for key, tmp := range j.Result {
+				exts := []interface{}{}
+				for k, v := range tmp {
+					text := qu.ObjToString(v.Value)
+					if text != "" {
+						text = in.RegPreBac.Reg.ReplaceAllString(text, in.RegPreBac.Replace)
+					}
+					j.Result[key][k].Value = text
+					exts = append(exts, text)
 				}
-				extinfo[k] = v.RegPreBac.Reg.ReplaceAllString(qu.ObjToString(val), v.RegPreBac.Replace)
-				doc[k] = extinfo[k] //结果覆盖原doc
+				extinfo[key] = exts
 			}
+			AddExtLog(j.SourceMid, j.Result, extinfo, in, t) //抽取日志
 		}
-		AddExtLog(before, extinfo, v, t) //抽取日志
 	}
-	return doc
+}
+
+//获取抽取结果map[string][]interface{},lua脚本使用
+func getResultMapForLua(j *ju.Job) map[string][]map[string]interface{} {
+	result := map[string][]map[string]interface{}{}
+	for key, val := range j.Result {
+		if result[key] == nil {
+			result[key] = []map[string]interface{}{}
+		}
+		for _, v := range val {
+			tmp := map[string]interface{}{
+				"field": v.Field,
+				"value": v.Value,
+				"type":  v.Type,
+				"key":   v.Key,
+			}
+			result[key] = append(result[key], tmp)
+		}
+	}
+	return result
 }
 
 //抽取日志
-func AddExtLog(before map[string]interface{}, extinfo interface{}, v *RegLuaInfo, t *TaskInfo) {
+func AddExtLog(sid string, before interface{}, extinfo interface{}, v *RegLuaInfo, t *TaskInfo) {
 	if !t.IsEtxLog {
 		return
 	}
@@ -475,11 +433,10 @@ func AddExtLog(before map[string]interface{}, extinfo interface{}, v *RegLuaInfo
 		"taskname":   t.Name,
 		"before":     before,
 		"extinfo":    extinfo,
-		"sid":        qu.BsonIdToSId(before["_id"]),
+		"sid":        sid,
 		"comeintime": time.Now().Unix(),
 	}
 	lock.Lock()
-
 	ExtLogs[t] = append(ExtLogs[t], logdata)
 	lock.Unlock()
 }

+ 256 - 0
src/jy/extract/extractInit.go

@@ -0,0 +1,256 @@
+// extractInit
+package extract
+
+import (
+	db "jy/mongodbutil"
+	qu "qfw/util"
+	"regexp"
+	"strings"
+)
+
+type RegLuaInfo struct { //正则或脚本信息
+	Code, Name, Field  string        //
+	RuleText           string        //
+	IsLua, IsHasFields bool          //IsHasFields脚本配置有属性字段
+	RegPreBac          *ExtReg       //
+	RegCore            *ExtReg       //
+	LFields            []interface{} //lua抽取字段属性组
+}
+type ExtReg struct {
+	Reg        *regexp.Regexp
+	Replace    string
+	Bextract   bool
+	ExtractPos map[string]int
+}
+type RuleCore struct {
+	LuaLogic  string        //进入逻辑
+	IsBlock   bool          //是否分块
+	RulePres  []*RegLuaInfo //前置规则
+	RuleBacks []*RegLuaInfo //后置规则
+	RuleCores []*RegLuaInfo //抽取规则
+}
+type TaskInfo struct {
+	Name, Version, TrackColl     string    //名称、版本、追踪记录表
+	FromDbAddr, FromDB, FromColl string    //抽取数据库地址、库名、表名
+	SaveColl, LastExtId          string    //抽取结果表、上次抽取信息id
+	DB                           *db.Pool  //数据库连接池
+	IsEtxLog                     bool      //是否开启抽取日志
+	ProcessPool                  chan bool //任务进程池
+}
+type Tag struct {
+	Type string //标签类型 string 字符串、regexp 正则
+	Key  string //
+}
+type ExtractTask struct {
+	Id        string            //任务id
+	IsRun     bool              //是否启动
+	Content   string            //信息内容
+	TaskInfo  *TaskInfo         //任务信息
+	RulePres  []*RegLuaInfo     //前置规则
+	RuleBacks []*RegLuaInfo     //后置规则
+	RuleCores []*RuleCore       //抽取规则
+	Tag       map[string][]*Tag //标签库
+}
+
+func init() {
+	TaskList = make(map[string]*ExtractTask)
+	go SaveExtLog()
+}
+
+//加载任务信息
+func (e *ExtractTask) InitTaskInfo() {
+	task, _ := db.Mgo.FindById("task", e.Id, nil)
+	if len(*task) > 1 {
+		e.TaskInfo = &TaskInfo{
+			Name:        (*task)["s_taskname"].(string),
+			Version:     (*task)["s_version"].(string),
+			TrackColl:   (*task)["s_trackcoll"].(string),
+			FromDbAddr:  (*task)["s_mgoaddr"].(string),
+			FromDB:      (*task)["s_mgodb"].(string),
+			FromColl:    (*task)["s_mgocoll"].(string),
+			SaveColl:    (*task)["s_mgosavecoll"].(string),
+			IsEtxLog:    qu.If(qu.IntAll((*task)["i_track"]) == 1, true, false).(bool),
+			LastExtId:   qu.ObjToString((*task)["s_extlastid"]),
+			ProcessPool: make(chan bool, qu.IntAllDef((*task)["i_process"], 1)),
+		}
+	} else {
+		return
+	}
+}
+
+//加载前置规则
+func (e *ExtractTask) InitRulePres() {
+	defer qu.Catch()
+	list, _ := db.Mgo.Find("rule_pre", `{"s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+	for _, v := range *list {
+		rinfo := &RegLuaInfo{
+			Code:  v["s_code"].(string),
+			Name:  v["s_name"].(string),
+			IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
+		}
+		if rinfo.IsLua {
+			rinfo.RuleText = v["s_luascript"].(string)
+		} else {
+			rinfo.RuleText = v["s_rule"].(string)
+			tmp := strings.Split(rinfo.RuleText, "__")
+			if len(tmp) == 2 {
+				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
+			} else {
+				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
+			}
+		}
+		e.RulePres = append(e.RulePres, rinfo)
+	}
+}
+
+//加载后置规则
+func (e *ExtractTask) InitRuleBacks() {
+	defer qu.Catch()
+	list, _ := db.Mgo.Find("rule_back", `{"s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+	for _, v := range *list {
+		rinfo := &RegLuaInfo{
+			Code:  v["s_code"].(string),
+			Name:  v["s_name"].(string),
+			IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
+		}
+		if rinfo.IsLua {
+			rinfo.RuleText = v["s_luascript"].(string)
+		} else {
+			rinfo.RuleText = v["s_rule"].(string)
+			tmp := strings.Split(rinfo.RuleText, "__")
+			if len(tmp) == 2 {
+				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
+			} else {
+				rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
+			}
+		}
+		e.RuleBacks = append(e.RuleBacks, rinfo)
+	}
+}
+
+//加载抽取规则
+func (e *ExtractTask) InitRuleCore() {
+	defer qu.Catch()
+	list, _ := db.Mgo.Find("rule_logic", `{"s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+	for _, vv := range *list {
+		if b, _ := vv["isuse"].(bool); !b {
+			continue
+		}
+		rcore := &RuleCore{}
+		rcore.LuaLogic = qu.ObjToString(vv["s_luascript"]) //是否进入逻辑脚本
+		rcore.IsBlock, _ = vv["isblock"].(bool)            //是否分块
+		//前置规则
+		rulePres := []*RegLuaInfo{}
+		plist, _ := db.Mgo.Find("rule_logicpre", `{"sid":"`+qu.BsonIdToSId(vv["_id"])+`","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+		for _, v := range *plist {
+			rinfo := &RegLuaInfo{
+				Code:  v["s_code"].(string),
+				Name:  v["s_name"].(string),
+				IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
+			}
+			if rinfo.IsLua {
+				rinfo.RuleText = v["s_luascript"].(string)
+			} else {
+				rinfo.RuleText = v["s_rule"].(string)
+				rinfo.Field = v["s_field"].(string)
+				tmp := strings.Split(rinfo.RuleText, "__")
+				if len(tmp) == 2 {
+					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
+				} else {
+					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
+				}
+			}
+			rulePres = append(rulePres, rinfo)
+		}
+		rcore.RulePres = rulePres
+
+		//后置规则
+		ruleBacks := []*RegLuaInfo{}
+		blist, _ := db.Mgo.Find("rule_logicback", `{"sid":"`+qu.BsonIdToSId(vv["_id"])+`","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+		for _, v := range *blist {
+			rinfo := &RegLuaInfo{
+				Code:  v["s_code"].(string),
+				Name:  v["s_name"].(string),
+				IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
+			}
+			if rinfo.IsLua {
+				rinfo.RuleText = v["s_luascript"].(string)
+			} else {
+				rinfo.RuleText = v["s_rule"].(string)
+				rinfo.Field = v["s_field"].(string)
+				tmp := strings.Split(rinfo.RuleText, "__")
+				if len(tmp) == 2 {
+					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: tmp[1]}
+				} else {
+					rinfo.RegPreBac = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Replace: ""}
+				}
+			}
+			ruleBacks = append(ruleBacks, rinfo)
+		}
+		rcore.RuleBacks = ruleBacks
+
+		//抽取规则
+		ruleCores := []*RegLuaInfo{}
+		clist, _ := db.Mgo.Find("rule_logicore", `{"sid":"`+qu.BsonIdToSId(vv["_id"])+`","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+		for _, v := range *clist {
+			if b, _ := v["isuse"].(bool); !b {
+				continue
+			}
+			rinfo := &RegLuaInfo{
+				Code:  v["s_code"].(string),
+				Name:  v["s_name"].(string),
+				IsLua: qu.If(v["s_type"].(string) == "1", true, false).(bool),
+			}
+			if rinfo.IsLua {
+				rinfo.RuleText = v["s_luascript"].(string)
+				rinfo.LFields, _ = v["s_fields"].([]interface{})
+				if len(rinfo.LFields) > 0 {
+					rinfo.IsHasFields = true
+				}
+			} else {
+				rinfo.RuleText = v["s_rule"].(string)
+				rinfo.Field = v["s_field"].(string)
+				tmp := strings.Split(rinfo.RuleText, "__")
+				if len(tmp) == 2 {
+					epos := strings.Split(tmp[1], ",")
+					posm := map[string]int{}
+					for _, v := range epos {
+						ks := strings.Split(v, ":")
+						if len(ks) == 2 { //(.*)招标公告(.*)__2:projectname,4:area
+							posm[ks[1]] = qu.IntAll(ks[0])
+						} else { //(.*)招标公告__2
+							posm[rinfo.Field] = qu.IntAll(ks[0])
+						}
+					}
+					rinfo.RegCore = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Bextract: true, ExtractPos: posm}
+				} else {
+					rinfo.RegCore = &ExtReg{Reg: regexp.MustCompile(tmp[0]), Bextract: false}
+				}
+			}
+			ruleCores = append(ruleCores, rinfo)
+		}
+		rcore.RuleCores = ruleCores
+
+		//
+		e.RuleCores = append(e.RuleCores, rcore)
+	}
+}
+
+//加载标签库
+func (e *ExtractTask) InitTag() {
+	defer qu.Catch()
+	e.Tag = map[string][]*Tag{}
+	//字符串标签库
+	list, _ := db.Mgo.Find("tagdetailinfo", `{"s_type":"字符串","s_version":"`+e.TaskInfo.Version+`"}`, nil, nil, false, -1, -1)
+	for _, v := range *list {
+		field := qu.ObjToString(v["s_field"])
+		if tmp, ok := v["content"].([]interface{}); ok {
+			for _, key := range tmp {
+				tag := &Tag{Type: "string", Key: key.(string)}
+				e.Tag[field] = append(e.Tag[field], tag)
+			}
+		}
+	}
+	//正则标签库
+
+}

+ 3 - 0
src/jy/pretreated/README.MD

@@ -0,0 +1,3 @@
+**预处理**
+转换文档-》想要的Article格式
+

+ 469 - 0
src/jy/pretreated/analykv.go

@@ -0,0 +1,469 @@
+package pretreated
+
+import (
+	u "jy/util"
+	"regexp"
+	"strings"
+)
+
+var Han = regexp.MustCompile("[\\p{Han}]")
+var Han1 = regexp.MustCompile("[^:;;,:,。. \u3000\u2003\u00a0\\s]")
+var Han2 = regexp.MustCompile("[^:;;,:,。.]")
+var Key = regexp.MustCompile("[:::]")
+var Time = regexp.MustCompile("[\\d]")
+var dh = regexp.MustCompile("[,,.]")
+var space = regexp.MustCompile("[\\s\\n \u3000\u2003\u00a0]+")
+var val = regexp.MustCompile("[^\\s\\n \u3000\u2003\u00a0,,。!;;]")
+var matchkh = map[string]string{
+	"(": ")",
+	"(": ")",
+	"【": "】",
+	"[": "]",
+	"[": "]",
+	"〖": "〗",
+}
+
+func GetKvFromtxt(con, tag string, from int) ([]*u.Kv, map[string]string, map[string]*u.Tag) {
+	res := FindKv(TextAfterRemoveTable(con), tag, from)
+	kvs := []*u.Kv{}
+	for _, k := range res.Keys {
+		v, _ := res.Map[k].(string)
+		if k != "" && v != "" {
+			kvs = append(kvs, &u.Kv{
+				Key:   k,
+				Value: v,
+			})
+		}
+	}
+	kv, tagKv := KvTagsToKV(kvs, tag, nil, from)
+	return kvs, kv, tagKv
+}
+
+type Line struct {
+	PreLine  *Line
+	NextLine *Line
+	Strs     []string
+	Str      string
+	Pos      int
+	Len      int
+	KV       *SortMap
+	IsKey    bool //是否只是key
+	Kn       int  //冒号个数
+	Spacen   int  //间隔空格个数
+	DJh      int  //逗号句号
+}
+
+func NewLine() *Line {
+	return &Line{
+		Strs: []string{},
+		KV:   NewSortMap(),
+	}
+}
+
+var LineKey = regexp.MustCompile("^[^,。]{2,10}[::]$")
+var DJh = regexp.MustCompile("[,,。]")
+var DunH = regexp.MustCompile("[、.]")
+
+func GetLines(con string) (res []*Line) {
+	res = []*Line{}
+	l1 := NewLine()
+	strings.IndexFunc(con, func(r rune) bool {
+		if r == 10 {
+			if len(l1.Strs) > 0 {
+				l1.Str = strings.Join(l1.Strs, "")
+				if !regexp.MustCompile("^[,,.。\\s \u3000\u2003\u00a0]$").MatchString(l1.Str) {
+					l1.Str = u.TrimLRSpace(l1.Str, "")
+					l1.Str = TimeHM.ReplaceAllString(l1.Str, "D$1H$2M")
+					l1.Strs = strings.Split(l1.Str, "")
+					res = append(res, l1)
+				}
+				l1 = NewLine()
+			}
+		} else {
+			s := string(r)
+			l1.Strs = append(l1.Strs, s)
+		}
+		return false
+	})
+	if len(l1.Strs) > 0 {
+		res = append(res, l1)
+	}
+	for k, l := range res {
+		if k == 0 && k < len(res)-1 {
+			l.NextLine = res[k+1]
+		} else if k == len(res)-1 {
+			l.PreLine = res[k-1]
+		} else {
+			l.PreLine = res[k-1]
+			l.NextLine = res[k+1]
+		}
+		if LineKey.MatchString(l.Str) {
+			l.IsKey = true
+		} else {
+			l.Kn = len(Key.FindAllString(l.Str, -1))
+			l.DJh = len(DJh.FindAllString(l.Str, -1))
+		}
+	}
+	return
+}
+
+func FindKv_v2(con, tag string) (m *SortMap) {
+	m = NewSortMap()
+	resLine := GetLines(con)
+	for i := 0; i < len(resLine); i++ {
+		l1 := resLine[i]
+		if l1.IsKey {
+			continue
+		} else {
+			if l1.Kn > 0 {
+				u.Debug("=--=", l1.Str)
+			} else {
+				if l1.Spacen == 1 && l1.DJh < 2 && l1.Len < 50 {
+					u.Debug("===", l1.Str)
+				} else {
+					u.Debug("???", l1.Str)
+				}
+			}
+		}
+	}
+	return
+}
+
+var TimeHM = regexp.MustCompile("[\\s \u3000\u2003\u00a0]*([01]{0,1}[0123456789]|2[0123])[::]([012345][0-9])[::]{0,1}")
+
+//from 1--全文 2--table td
+func FindKv(con, tag string, from int) (m *SortMap) {
+	if from == 2 || from == 3 {
+		con = RemoveWarpOfTdVal(con)
+	}
+	//FindKv_v2(con, tag)
+	matchMap := map[string]map[string]bool{
+		"代理机构": map[string]bool{},
+		"中标单位": map[string]bool{},
+		"采购单位": map[string]bool{},
+	}
+	m = NewSortMap()
+	strs := [][]string{}
+	s1 := []string{}
+	//断行
+	strings.IndexFunc(con, func(r rune) bool {
+		if r == 10 {
+			if len(s1) > 0 {
+				str := strings.Join(s1, "")
+				str = u.TrimLRSpace(str, "")
+				str = TimeHM.ReplaceAllString(str, "D${1}H${2}M")
+				s1 = strings.Split(str, "")
+				if len(s1) > 0 {
+					strs = append(strs, s1)
+				}
+				s1 = []string{}
+			}
+		} else {
+			s := string(r)
+			s1 = append(s1, s)
+		}
+		return false
+	})
+
+	if len(s1) > 0 {
+		str := strings.Join(s1, "")
+		str = u.TrimLRSpace(str, "")
+		//u.Debug(str, TimeHM.ReplaceAllString(str, "D${1}H${2}M"))
+		str = TimeHM.ReplaceAllString(str, "D${1}H${2}M")
+		s1 = strings.Split(str, "")
+		if len(s1) > 0 {
+			strs = append(strs, s1)
+		}
+	}
+	//查找
+	LastStr := ""
+	for k0 := 0; k0 < len(strs); k0++ {
+		s1 := strs[k0]
+		//u.Debug(strings.Join(s1, ""))
+		str1 := strings.Join(s1, "")
+		k := ""
+		v := ""
+		flag := 0
+		pos1, pos2 := -1, -1
+		bkh := false
+		skh := ""
+		if !Key.MatchString(str1) { //此行没有冒号
+			if k0 > 0 {
+				tm1 := strs[k0-1]
+				if len([]rune(LastStr)) > 2 && len(tm1) < 8 && Key.MatchString(tm1[len(tm1)-1:][0]) && len([]rune(str1)) < 30 {
+					//u.Debug(LastStr, str1)
+					k = strings.Join(tm1[:len(tm1)-1], "")
+					v = str1
+					if k0 < len(strs)-1 {
+						s2 := u.TrimLRSpace(strings.Join(strs[k0+1], ""), "")
+						if len([]rune(s2)) < 10 && !Key.MatchString(s2) {
+							v += s2
+							k0++
+						}
+					}
+					keydetail(k, v, m, tag, k0, strs, matchMap, from)
+				}
+			}
+			LastStr = ""
+			continue
+		} else {
+			//u.Debug("---===----", str1)
+			LastStr = str1
+			for k1 := 0; k1 < len(s1); k1++ {
+				s := s1[k1]
+				if matchkh[s] != "" {
+					skh = matchkh[s]
+					bkh = true
+				}
+				if bkh {
+					if skh == s {
+						bkh = false
+					}
+					if flag == 1 {
+						k += s
+					} else if flag == 2 {
+						v += s
+					}
+					continue
+				}
+				if flag == 0 {
+					k = ""
+					v = ""
+					pos1, pos2 = -1, -1
+					flag = 1
+				}
+				if flag == 1 {
+					if Han1.MatchString(s) || (k != "" && Han2.MatchString(s)) {
+						k += s
+					} else if Key.MatchString(s) && k != "" {
+						flag = 2
+					} else {
+						flag = 0
+					}
+				} else if flag == 2 {
+					if val.MatchString(s) || (dh.MatchString(s) && k1 > 0 && k1 < len(s1)-1 && Time.MatchString(s1[k1-1]) && Time.MatchString(s1[k1+1])) {
+						if pos1 < 0 {
+							pos1 = k1
+						}
+						continue
+					} else {
+						be := false
+						if space.MatchString(s) {
+							temp := s1[k1+1:]
+							//()()[]【】
+							m1 := k1
+							bkh1 := false
+							skh1 := ""
+							for k2, v2 := range temp {
+								if k2 == len(temp)-1 {
+									be = true
+								}
+								if matchkh[v2] != "" {
+									bkh1 = true
+									skh1 = matchkh[v2]
+									continue
+								} else if bkh1 {
+									if v2 == skh1 {
+										bkh1 = false
+									}
+									continue
+								} else if space.MatchString(v2) {
+									continue
+								} else if !val.MatchString(v2) {
+									k1 = m1 + k2 + 1
+									break
+								} else {
+									if pos1 < 0 {
+										//u.Debug("-----", pos1)
+										pos1 = k1 + k2 + 1
+									}
+								}
+								if Key.MatchString(v2) && k2 > 0 && k2 < len(temp)-1 {
+									if Time.MatchString(temp[k2-1]) && Time.MatchString(temp[k2+1]) {
+										//u.Debug(v2, temp[k2-1], temp[k2+1])
+										k1 = m1 + k2 + 1
+									} else {
+										//倒着
+										for i := k2; i > k1-m1-1; i-- {
+											if !val.MatchString(temp[i]) {
+												k1 = m1 + i + 1
+												break
+											}
+										}
+										break
+									}
+								}
+							}
+						}
+						if be {
+							k1 = len(s1) //直接跳到最后
+						}
+						if pos2 < 0 && pos2 < pos1 {
+							pos2 = k1
+						}
+						//					u.Debug(pos1, pos2, k1, len(s1))
+						if pos1 > -1 && pos2 > pos1 {
+							v = strings.Join(s1[pos1:pos2], "")
+							flag = 0
+							keydetail(k, v, m, tag, k0, strs, matchMap, from)
+						} else {
+							//u.Debug(k, pos1, pos2)
+							flag = 0
+						}
+					}
+				}
+			}
+			if flag == 2 {
+				if pos2 > pos1 {
+					v = strings.Join(s1[pos1:pos2], "")
+				} else if pos1 > 0 {
+					v = strings.Join(s1[pos1:], "")
+				}
+				if v != "" {
+					flag = 0
+					keydetail(k, v, m, tag, k0, strs, matchMap, from)
+				}
+				//u.Debug(k, v)
+			}
+		}
+	}
+	//	for _, kk := range m.Keys {
+	//		u.Debug(kk, m.Map[kk])
+	//	}
+	return
+}
+
+func keydetail(k, v string, m *SortMap, tag string, pos int, strs [][]string, matchMap map[string]map[string]bool, from int) {
+	k = space.ReplaceAllString(k, "")
+	if len([]rune(k)) > 1 {
+		if len([]rune(k)) < 5 && strings.HasPrefix(k, "联系") || ContactInfoMustReg.MatchString(k) {
+			num := 0
+			bf := false
+			for i := len(m.Keys) - 1; i > -1; i-- {
+				num++
+				if from == 1 && !ContactType["代理机构"].MatchString(k) && ContactType["代理机构"].MatchString(m.Keys[i]) && !IsContactKvHandle(k, matchMap["代理机构"]) {
+					matchMap["代理机构"][k] = true
+					k = "代理机构" + k
+					bf = true
+					break
+				}
+				if !filter_zbdw_ky.MatchString(k) && filter_zbdw_ky.MatchString(m.Keys[i]) && !IsContactKvHandle(k, matchMap["中标单位"]) {
+					matchMap["中标单位"][k] = true
+					k = "中标单位" + k
+					bf = true
+					break
+				}
+				if from == 1 && !ContactType["采购单位"].MatchString(k) && ContactType["采购单位"].MatchString(m.Keys[i]) && !IsContactKvHandle(k, matchMap["采购单位"]) {
+					matchMap["采购单位"][k] = true
+					k = "采购单位" + k
+					bf = true
+					break
+				}
+				//if num > 0 {
+				break
+				//}
+			}
+			if !bf {
+				//k = "采购人" + k
+				//取出上一行
+				if pos > 0 {
+					if len(strs[pos-1]) < 20 {
+						str := space.ReplaceAllString(strings.Join(strs[pos-1], ""), "")
+						if from == 1 && ContactType["代理机构"].MatchString(str) && !IsContactKvHandle(k, matchMap["代理机构"]) {
+							matchMap["代理机构"][k] = true
+							k = "代理机构" + k
+						} else if filter_zbdw_ky.MatchString(str) && !IsContactKvHandle(k, matchMap["中标单位"]) {
+							matchMap["中标单位"][k] = true
+							k = "中标单位" + k
+						} else if from == 1 && ContactType["采购单位"].MatchString(str) && !IsContactKvHandle(k, matchMap["采购单位"]) {
+							matchMap["采购单位"][k] = true
+							k = "采购单位" + k
+						}
+					}
+				}
+			}
+		} else if len([]rune(k)) == 2 {
+			if filter_zbje_jd.MatchString(k) { //钱
+				if tag != "" && filter_tag_zb.MatchString(tag) {
+					k = "中标" + k
+				} else {
+					num := 0
+					for i := len(m.Keys) - 1; i > -1; i-- {
+						num++
+						if filter_zbdw_ky.MatchString(m.Keys[i]) {
+							k = "中标" + k
+							break
+						}
+						if num > 2 {
+							break
+						}
+
+					}
+				}
+
+			}
+
+		}
+		//u.Debug(k, v)
+		if m.Map[k] == nil {
+			m.AddKey(k, v)
+		}
+	}
+}
+
+//时间处理、换行优先级|
+func FindKv_v1(con string) (m map[string]string) {
+	m = map[string]string{}
+	k := ""
+	v := ""
+	flag := 0
+	strings.IndexFunc(con, func(r rune) bool {
+		s := string(r)
+		if flag == 0 {
+			k = ""
+			v = ""
+			flag = 1
+		}
+		if flag == 1 {
+			if Han.MatchString(s) {
+				k += s
+			} else if Key.MatchString(s) && k != "" {
+				flag = 2
+			} else {
+				flag = 0
+			}
+		} else if flag == 2 {
+			if v == "" {
+				if space.MatchString(s) {
+
+				} else if val.MatchString(s) && !Key.MatchString(s) {
+					v += s
+				} else {
+					flag = 0
+				}
+			} else {
+				if val.MatchString(s) {
+					if Key.MatchString(k) {
+						if (regexp.MustCompile("(时间|日期)").MatchString(v) || regexp.MustCompile("(时间|日期)").MatchString(k)) && regexp.MustCompile("[^\\d][012]?[0-9]").MatchString(k) {
+							v += s
+						} else if regexp.MustCompile("^[\\p{Han}]$").MatchString(v) {
+							k = v
+							v = ""
+							flag = 1
+						}
+					} else {
+						v += s
+					}
+				} else if k != "" && v != "" {
+					u.Debug(k, "=", v)
+					flag = 0
+				}
+			}
+		}
+		return false
+	})
+	if flag == 2 && k != "" && v != "" {
+		u.Debug(k, "=", v)
+	}
+	return
+}

+ 149 - 0
src/jy/pretreated/analystep.go

@@ -0,0 +1,149 @@
+/**
+信息预处理入口
+**/
+package pretreated
+
+import (
+	"jy/util"
+	qutil "qfw/util"
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+)
+
+func AnalyStart(job *util.Job) {
+	con := job.Content
+	//全文的需要修复表格
+	con = RepairCon(con)
+	//格式化正文
+	con = formatText(con, "all")
+	job.Content = con
+	//
+	tabs, ration := ComputeConRatio(con, 1)
+	if len(tabs) > 0 {
+		newcon, newtabs, newration := findBigText(con, ration, tabs)
+		if newcon != "" && newration == 0 {
+			con = newcon
+			tabs = newtabs
+			ration = newration
+		}
+	}
+	blockArrays, _ := DivideBlock(con, 1)
+	if len(blockArrays) > 0 { //有分块
+		for _, bl := range blockArrays {
+			//块中再查找表格(块,处理完把值赋到块)
+			t1, _ := ComputeConRatio(bl.Text, 2)
+			if len(t1) > 0 {
+				tabres := AnalyTableV2(t1, job.Category, bl.Title, bl.Text, 2, job.SourceMid)
+				processTableResult(tabres, bl)
+				if bl.Title == "" && tabres.BlockTag != "" {
+					bl.Title = tabres.BlockTag
+				}
+			}
+			job.Block = append(job.Block, bl)
+		}
+	} else { //未分块,创建分块
+		bl := &util.Block{}
+		newCon := con
+		if len(tabs) > 0 { //解析表格逻辑(article,处理完把值赋到article)
+			newCon = TextAfterRemoveTable(con)
+			//table中kv覆盖全文正则的kv
+			tabres := AnalyTableV2(tabs, job.Category, "", con, 1, job.SourceMid)
+			processTableResult(tabres, bl)
+		}
+		//调用kv解析
+		bl.ColonKV = GetKVAll(newCon, "", 1)
+		bl.SpaceKV = spacekvEntity.entrance(newCon, "")
+		job.Block = append(job.Block, bl)
+	}
+}
+
+//分析table解析结果
+func processTableResult(tabres *TableResult, block *util.Block) {
+	//解析结果中的kv
+	kv := map[string]string{}
+	for k, v := range tabres.SortKV.Map {
+		kv[k] = qutil.ObjToString(v)
+	}
+	block.TableKV = &util.JobKv{Kv: kv}
+}
+
+//一行多列 一列多行,按照分块逻辑处理
+//ration==1 遍历所有tabs,ration!=1 tabs只有一个
+func tableDivideBlock(con string, ration float32, tabs []*goquery.Selection) string {
+	if len(tabs) != 1 {
+		return ""
+	}
+	for _, tab := range tabs {
+		content := ""
+		tbody := tab.ChildrenFiltered("tbody,thead")
+		var tr *goquery.Selection
+		if tbody.Length() == 1 {
+			tr = tbody.ChildrenFiltered("tr")
+		} else {
+			tr = tab.ChildrenFiltered("tr")
+		}
+		if tr.Length() == 1 {
+			tds := tr.ChildrenFiltered("td")
+			tds.Each(func(index int, sn *goquery.Selection) {
+				ret, _ := sn.Html()
+				if strings.TrimSpace(ret) != "" {
+					content += ret + "\n"
+				}
+			})
+		} else {
+			flag := true
+			tr.EachWithBreak(func(index int, sn *goquery.Selection) bool {
+				th := sn.ChildrenFiltered("th")
+				td := sn.ChildrenFiltered("td")
+				if th.Length() > 0 || td.Length() > 1 {
+					flag = false
+					return false
+				} else if td.Length() == 1 {
+					ret, _ := td.Html()
+					if strings.TrimSpace(ret) != "" {
+						content += ret + "\n"
+					}
+				}
+				return true
+			})
+			if !flag {
+				return ""
+			}
+		}
+		if content != "" {
+			content = regMoreWrap.ReplaceAllString(content, "\n")
+			content = regEndWrap.ReplaceAllString(content, "")
+			doc, _ := goquery.NewDocumentFromReader(strings.NewReader(con))
+			doc.Find("table").Eq(0).ReplaceWithHtml(content)
+			con, _ = doc.Html()
+		}
+	}
+	return con
+}
+
+//查找大文本,5次
+func findBigText(con string, r float32, t []*goquery.Selection) (content string, tabs []*goquery.Selection, ration float32) {
+	content = tableDivideBlock(con, r, t)
+	if content == "" {
+		return
+	}
+	for i := 0; i < 4; i++ {
+		if content != "" {
+			tabs, ration = ComputeConRatio(content, 1)
+			if len(tabs) > 0 {
+				content = tableDivideBlock(content, ration, tabs)
+				if content == "" {
+					return
+				}
+			} else {
+				doc, _ := goquery.NewDocumentFromReader(strings.NewReader(con))
+				content = doc.Text()
+				return
+			}
+		} else {
+			return
+		}
+	}
+	return
+}

+ 2168 - 0
src/jy/pretreated/analytable.go

@@ -0,0 +1,2168 @@
+package pretreated
+
+import (
+	"fmt"
+	u "jy/util"
+	qutil "qfw/util"
+	"regexp"
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+)
+
+/**
+全局变量,主要是一堆判断正则
+**/
+var (
+	//清理表格中是key中包含的空格或数字等
+	tablekeyclear = regexp.MustCompile("[\\s\u3000\u2003\u00a0\\n、.,.。、_/]+|^[\\d一二三四五六七八九十]+[、.]*|[((【\\[].*?[))】\\]]")
+	//判断key是金额,对万元的处理
+	moneyreg = regexp.MustCompile("(预算|费|价|额|规模|投资)")
+	//根据表格的内容判断是不是表头,如果含有金额则不是表头
+	MoneyReg = regexp.MustCompile("^[\\s  ::0-9.万元()()人民币¥$]+$")
+	//判断分包时
+	moneyNum = regexp.MustCompile("[元整¥万]")
+	//对隐藏表格的判断
+	display = regexp.MustCompile("(?i).*?display\\s?[:]\\s?none.*")
+
+	//---------------
+	//求是分包的概率
+	//根据表格的标签对分包进行打分
+	TableMultiPackageReg_4 = regexp.MustCompile("(标段|分包|包段|划分|子包|标包|合同段)")
+	TableMultiPackageReg_2 = regexp.MustCompile("(概况|范围|情况|内容|详细|结果|信息)")
+	//在判断分包打分前过虑表格key
+	FilterKey_2 = regexp.MustCompile("招标|投标|项目")
+	//根据表格的key进行分包打分
+	FindKey_2 = regexp.MustCompile("([分子][包标](号)?|标[号项段包](划分)?|包件?[号段名数])")
+	//对值进行分包判断
+	FindVal_1  = regexp.MustCompile("[第]?([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)((子|合同|分|施工|监理)?(标段?|包|合同段|标包))|((子|合同|分|施工|监理)?(标|包)(段|号)?)[  \u3000\u2003\u00a0]*([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)")
+	FindVal2_1 = regexp.MustCompile("([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ\\-]+)|^(设计|施工|监理|验收)[分子]?[标包]?[段号]?$")
+	//判断分包前排除
+	excludeKey = regexp.MustCompile("(涉及包号|包件号?|项目标号|规格|型号|招标范围|业绩|废标)|(^编号$)|([^包段标]编号)") //编号|划分
+	//-------------
+
+	cut = u.NewCut()
+
+	//清理表格标签正则
+	ClearTagReg = regexp.MustCompile("<[^>]*?>|[\\s\\n\\r]*$")
+	//查找表格标签正则
+	ttagreg = regexp.MustCompile("(?s)([^\\n::。,;\\s\u3000\u2003\u00a0]{2,30})[::]?[^::。;!\\n]{0,35}[\\s\\n]*$")
+
+	//判断表格是表头的概率
+	checkval = float32(0.6)
+
+	//tdval_reg = regexp.MustCompile(`([\p{Han}][\p{Han}\s、()\\(\\)]{1,9})[::]([^::\\n。]{5,60})(?:[;;,,.。\\n\\t\\s])?`)
+	//空格替换
+	repSpace = regexp.MustCompile("[\\s\u3000\u2003\u00a0::]+|\\\\t+")
+
+	//对表格kv的处理
+	//对不能标准化的key做批识
+	filter_tag_zb = regexp.MustCompile("(中标|成交|投标)[\\p{Han}]{0,6}(情况|结果|信息|明细)")
+	//中标金额
+	//包含以下字眼做标准化处理
+	filter_zbje_k = regexp.MustCompile("(中标|成交|总|拦标|合同|供[应货]商|报)[\\p{Han}、]{0,6}(价|额|[大小]写|[万亿]?元).{0,4}$")
+	//简单判断金额
+	filter_zbje_jd = regexp.MustCompile("^[^售]{0,4}(价|额).{0,4}$")
+	//且排队以下字眼的key
+	filter_zbje_kn = regexp.MustCompile("得分|打分|时间|业绩|须知|分$")
+	//且值包含以下字眼
+	filter_zbje_v = regexp.MustCompile("[¥$$0-9一二三四五六七八九十,,〇零点..壹贰叁肆伍陆柒捌玖拾百佰千仟万亿億元圆角分整正()::大小写]{2,16}")
+
+	//中标单位的处理
+	//包含以下字眼的Key标准化
+	filter_zbdw_ky = regexp.MustCompile("(中标|成交|拦标|合同|选中|投标|拟|预|最终)[\\p{Han}、]{0,6}(供[应货]商|企业|单位|人|机构)(名称)?.{0,4}$")
+	//简单判断
+	filter_zbdw_jd = regexp.MustCompile("(投标|成交|中标|合同)(供应商|单位|人|名称).{0,4}$")
+	//且不包含以下字眼
+	filter_zbdw_kn = regexp.MustCompile("第[2二3三4四5五]|得分|地址")
+	//且值包含以下字眼
+	filter_zbdw_v = regexp.MustCompile("(公司|集团|研究院|设计院|局|厂|部|站|城|店|市|所|处)$|([^购]中心|办公|用品)")
+
+	//Tg = map[string]interface{}{}
+	//一些表格没有表头,是空的,对值是排序的做处理对应 NullTxBid
+	NullTdReg      = regexp.MustCompile("(首选|第[一二三四五1-5])(中标|成交)?(名|(候选|排序)?(人|单位|供应商))")
+	NullTxtBid     = "成交供应商排名"
+	projectnameReg = regexp.MustCompile("((公开)?招标)*[((第]*[一二三四五六七八九十a-zA-Z0-9]+(标段|包|标|段)[))]*$")
+	MhSpilt        = regexp.MustCompile("[::]")
+	//识别采购单位联系人、联系电话、代理机构联系人、联系电话
+	ContactInfoVagueReg = regexp.MustCompile("邮政编码|邮编|(征求意见|报名审核购买)?((联系人?|办公)?((电话([//]传真)?|手机)(号码)?|邮箱(地址)?|(地(址|点)))|(联系|收料)(人(姓名)?|方式)|传真|电子邮件|(主要负责|项目(负责|联系)|(项目)?经办)人)|采购方代表")
+	ContactInfoMustReg  = regexp.MustCompile("^(" + ContactInfoVagueReg.String() + ")$")
+	ContactType         = map[string]*regexp.Regexp{
+		"采购单位": regexp.MustCompile("(采购(项目.{2}|服务)?|比选|询价|发布人?|甲|招标(服务)?|建设|委托|发包|业主|使用|谈判|本招标项目经办|征求意见联系|项目实施)(人|单位|部门|机构|机关|(执行)?方)|(项目|建(库|设))单位|招标人信息|采购中心地址|业主|收料人"),
+		"代理机构": regexp.MustCompile("(代理|受托).{0,2}(人|方|单位|公司|机构)|招标机构|采购代理"),
+	}
+	ContactBuyerPersonFilterReg = regexp.MustCompile("(管理局)$")
+	MultipleValueSplitReg       = regexp.MustCompile("[,,、\\s\u3000\u2003\u00a0]")
+	BuyerContacts               = []string{"采购单位联系人", "采购单位联系电话", "采购单位联系地址"}
+	FilterSerial                = regexp.MustCompile(".+[、..::,]")
+	filterTableWror             = regexp.MustCompile("班子成员")
+)
+
+//在解析时,判断表格元素是否隐藏
+func IsHide(g *goquery.Selection) (b bool) {
+	style, exists := g.Attr("style")
+	if exists {
+		b = display.MatchString(style)
+	}
+	return
+}
+
+//对表格的key进行标准化处理,多个k相同时,出现覆盖问题
+func CommonDataAnaly(k, tabletag, tabledesc string, v interface{}) (k1 []string, weight []int, v1, returntag string, b bool) {
+	k1 = []string{}
+	weight = []int{}
+	tk := k
+	if sv, sok := v.(string); sok { //取KV
+		v1 = sv
+	} else if sv, sok := v.([]string); sok { //是数组先默认取第一个
+		v1 = sv[0]
+	}
+	//对值单位的处理
+	if moneyreg.MatchString(tk) {
+		v1 += GetMoneyUnit(tk, v1)
+	}
+	//先清理key
+	//u.Debug(1, k, v1)
+	k = ClearKey(k, 2)
+	//u.Debug(2, k)
+	//取标准key
+	res := u.GetTags(k)
+	if len(res) == 0 && tk != k {
+		res = u.GetTags(tk)
+	}
+	//	if len(res) == 0 {
+	//		go u.AddtoNoMatchMap(tk)
+	//	}
+	//当取到标准化值时,放入数组
+	if len(res) > 0 {
+		b = true
+		for _, t1 := range res {
+			//u.Debug(k, k1, t1.Value, t1.Weight)
+			k1 = append(k1, t1.Value)
+			weight = append(weight, t1.Weight)
+		}
+		//k1 = res[0].Value
+	}
+	//没有取到标准化key时,对中标金额和中标单位的逻辑处理
+	if !b {
+		if filter_zbje_k.MatchString(k) && !filter_zbje_kn.MatchString(k) && filter_zbje_v.MatchString(v1) {
+			if tabletag == "" {
+				returntag = "中标情况"
+			}
+			k1 = append(k1, "中标金额")
+			weight = append(weight, -100)
+			b = true
+		} else if filter_zbdw_ky.MatchString(k) && !filter_zbdw_kn.MatchString(k) &&
+			filter_zbdw_v.MatchString(v1) {
+			k1 = append(k1, "中标单位")
+			weight = append(weight, -100)
+			if tabletag == "" {
+				returntag = "中标情况"
+			}
+			b = true
+		}
+	}
+	//对上一步没有取到标准化key的进一步处理
+	if !b {
+		if tabletag == "" {
+
+		}
+		if filter_tag_zb.MatchString(tabletag) || filter_tag_zb.MatchString(tabledesc) {
+			//u.Debug(v1, k, "-----", filter_zbdw_jd.MatchString(k), filter_zbdw_v.MatchString(v1))
+			if filter_zbje_jd.MatchString(k) && !filter_zbje_kn.MatchString(k) && filter_zbje_v.MatchString(v1) {
+				k1 = append(k1, "中标金额")
+				weight = append(weight, -100)
+				b = true
+			} /*else if filter_zbdw_jd.MatchString(k) && filter_zbdw_v.MatchString(v1) {
+				k1 = append(k1, "中标单位")
+				weight = append(weight, -100)
+				b = true
+			}*/
+		}
+	}
+	return
+}
+
+//对解析后的表格的kv进行过滤
+func (table *Table) KVFilter() {
+	//1.标准化值查找
+	//2.对数组的处理
+	//3.对分包的处理
+	//4.对KV的处理
+	//判断表格是否有用,调用abandontable正则数组进行判断
+	//遍历每一行
+	for _, tr := range table.TRs {
+		for _, td := range tr.TDs {
+			//u.Debug(td.BH, td.MustBH, td.Val, td.SortKV.Map)
+			bc := false
+			if !td.BH {
+				//表头是否是无用内容
+				if td.HeadTd != nil {
+					bc, _, _, _, _ = CheckCommon(td.HeadTd.Val, "abandontable")
+				}
+			}
+			if !bc {
+				//td元素有内嵌kv,遍历放入table的Kv中
+				if len(td.SortKV.Keys) > 0 {
+					for _, k3 := range td.SortKV.Keys {
+						_val := td.SortKV.Map[k3]
+						//thisFlag := false
+						if td.HeadTd != nil && len([]rune(k3)) < 4 {
+							k3 = td.HeadTd.Val + k3
+						}
+						if table.SortKV.Map[k3] == nil {
+							//u.Debug(k3, _val)
+							//if !thisFlag || (thisFlag && table.SortKV.Map[k3] == nil) {
+							table.SortKV.AddKey(k3, _val)
+						}
+					}
+				}
+			}
+			//td有子表格的处理
+			//u.Debug(td.BH, td.Val, td.SonTableResult)
+			if td.SonTableResult != nil {
+				//u.Debug(td.SonTableResult.SortKV.Map, "-------", td.SonTableResult.Tabs)
+				for _, k3 := range td.SonTableResult.SortKV.Keys {
+					if table.StandKV[k3] == "" || td.SonTableResult.SortKVWeight[k3] > table.StandKVWeight[k3] {
+						table.StandKV[k3] = qutil.ObjToString(td.SonTableResult.SortKV.Map[k3])
+						table.StandKVWeight[k3] = td.SonTableResult.SortKVWeight[k3]
+					}
+				}
+				//中标候选人排序
+				if table.WinnerOrder == nil || len(table.WinnerOrder) == 0 {
+					table.WinnerOrder = td.SonTableResult.WinnerOrder
+				} else {
+					winnerOrderEntity.Merge(table.WinnerOrder, td.SonTableResult.WinnerOrder)
+				}
+			}
+		}
+	}
+	as := NewSortMap()
+	//表格描述处理,对成交结果的处理
+	for _, k := range table.SortKV.Keys {
+		if regexp.MustCompile("(成交|中标|候选|排名|名次|供应商排序)").MatchString(k) {
+			table.Desc += "成交结果,"
+		}
+	}
+	//遍历table.sortkv,进行过滤处理,并放入标准化KV中,如果值是数组跳到下一步处理
+	for _, k := range table.SortKV.Keys {
+		if regexp.MustCompile("^单价").MatchString(k) {
+			continue
+		}
+		v := table.SortKV.Map[k]
+		//u.Debug(k, v)
+		if _, ok := v.(string); ok {
+			k = regSpliteSegment.ReplaceAllString(regReplAllSpace.ReplaceAllString(k, ""), "")
+			k1, w1, v1, tag, b := CommonDataAnaly(k, table.Tag, table.Desc, v)
+			//u.Debug(k, v, k1, w1, v1, tag, b)
+			if b {
+				//降低冒号值的权重
+				if MhSpilt.MatchString(v1) {
+					for pos, _ := range k1 {
+						w1[pos] -= 50
+					}
+				}
+				if tag != "" && table.Tag == "" {
+					table.Tag = tag
+				}
+				for pos, k2 := range k1 {
+					if table.StandKV[k2] == "" || w1[pos] > table.StandKVWeight[k2] {
+						table.StandKV[k2] = v1 //本节点
+						table.StandKVWeight[k2] = w1[pos]
+					}
+					//					 else if k2 == "中标金额" {
+					//						//						u.Debug(qutil.Float64All(v1), qutil.Float64All(table.StandKV[k2]))
+					//						if qutil.Float64All(v1) > qutil.Float64All(table.StandKV[k2]) {
+					//							table.StandKV[k2] = v1
+					//						}
+					//					}
+				}
+			}
+		} else {
+			//u.Debug(k, v, "---------")
+			as.AddKey(k, v)
+		}
+	}
+	//处理值是数组的kv放入标准化kv中
+	checkKey := map[int]bool{}
+	for kn, k := range as.Keys {
+		v := as.Map[k]
+		vm, ok := v.([]map[string]interface{})
+		if k == NullTxtBid && ok {
+			if table.WinnerOrder == nil {
+				table.WinnerOrder = []map[string]interface{}{}
+			}
+			table.WinnerOrder = append(table.WinnerOrder, vm...)
+		} else {
+			//增加候选人排序逻辑
+			if table.WinnerOrder == nil && !checkKey[kn] {
+				if vs1, ok := v.([]string); ok {
+					smap := make([]map[string]interface{}, len(vs1))
+					for n1, _ := range vs1 {
+						smap[n1] = map[string]interface{}{}
+					}
+					for kn1, k := range as.Keys[kn:] {
+						v := as.Map[k]
+						if ContactType["采购单位"].MatchString(k) || ContactType["代理机构"].MatchString(k) {
+							continue
+						}
+						if vs, ok := v.([]string); ok && len(vs) == len(vs1) {
+							res, _, _, _, repl := CheckCommon(k, "bidorder")
+							kv := ""
+							if !res {
+								kt := u.GetTags(filterThText.ReplaceAllString(ClearKey(k, 2), ""))
+								if kt.Len() > 0 {
+									kv = kt[0].Value
+								}
+							}
+							if res || kv != "" { //连续往下找几个key
+								checkKey[kn+kn1] = true
+								if repl == "sort" {
+									for vsk, vsv := range vs {
+										smap[vsk]["sortstr"] = vsv
+										smap[vsk]["sort"] = GetBidSort(vsv, vsk+1)
+									}
+								} else if repl == "entname" || kv == "中标单位" {
+									for vsk, vsv := range vs {
+										smap[vsk]["entname"] = winnerOrderEntity.clear("中标单位", vsv)
+									}
+								} else if kv == "中标金额" {
+									for vsk, vsv := range vs {
+										p1 := qutil.Float64All(smap[vsk]["price"])
+										p2 := qutil.Float64All(vsv)
+										if p2 > p1 {
+											smap[vsk]["price"] = winnerOrderEntity.clear("中标金额", vsv+GetMoneyUnit(k, vsv))
+										}
+									}
+								}
+							}
+						} else {
+							//break
+						}
+					}
+					newSmap := []map[string]interface{}{}
+					for _, smap_v := range smap {
+						if len(smap_v) > 0 {
+							newSmap = append(newSmap, smap_v)
+						}
+					}
+					if len(newSmap) > 0 {
+						table.WinnerOrder = newSmap
+					}
+				}
+			}
+			k1, w1, v1, tag, b := CommonDataAnaly(k, table.Tag, table.Desc, v)
+			if b {
+				if tag != "" && table.Tag == "" {
+					table.Tag = tag
+				}
+				for pos, k2 := range k1 {
+					if table.StandKV[k2] == "" || w1[pos] > table.StandKVWeight[k2] {
+						table.StandKV[k2] = v1 //本节点
+						table.StandKVWeight[k2] = w1[pos]
+					}
+					//				else if k2 == "中标金额" {
+					//					if qutil.Float64All(v1) > qutil.Float64All(table.StandKV[k2]) {
+					//						table.StandKV[k2] = v1
+					//					}
+					//				}
+				}
+			}
+		}
+	}
+	//
+	if filterTableWror.MatchString(table.Tag) {
+		table.WinnerOrder = nil
+	}
+	//
+	if len(table.WinnerOrder) > 0 || !table.BPackage {
+		winnerOrder := []map[string]interface{}{}
+		maxSort := 0
+		//调整顺序
+		for i := 0; i < 2; i++ {
+			for _, v := range table.WinnerOrder {
+				sortstr, _ := v["sortstr"].(string)
+				if (i == 0 && sortstr == "") || (i == 1 && sortstr != "") {
+					continue
+				}
+				sort, _ := v["sort"].(int)
+				if i == 0 {
+					if maxSort == 0 || sort > maxSort {
+						maxSort = sort
+					}
+				} else {
+					maxSort++
+					v["sort"] = maxSort
+				}
+				winnerOrder = append(winnerOrder, v)
+			}
+			if len(winnerOrder) == len(table.WinnerOrder) {
+				break
+			}
+		}
+		table.WinnerOrder = winnerOrder
+		winnerOrder = []map[string]interface{}{}
+	L:
+		for _, tr := range table.TRs {
+			for _, td := range tr.TDs {
+				winnerOrder = winnerOrderEntity.Find(td.Val, true, 3)
+				if len(winnerOrder) > 0 {
+					break L
+				}
+			}
+		}
+		if len(table.WinnerOrder) > 0 {
+			//中标候选人合并
+			winnerOrderEntity.Merge(table.WinnerOrder, winnerOrder)
+			if table.StandKV["中标单位"] == "" {
+				ent := table.WinnerOrder[0]["entname"]
+				if ent != nil {
+					table.StandKV["中标单位"], _ = ent.(string)
+					table.StandKVWeight["中标单位"] = -25
+				}
+			}
+		} else if !table.BPackage {
+			if len(winnerOrder) > 1 {
+				table.WinnerOrder = winnerOrder
+			}
+		}
+	}
+	//对中标候选人进行排序
+	winnerOrderEntity.Order(table.WinnerOrder)
+	//该表格有一个分包,并且有中标候选人排序的情况下,把中标候选人放到包里面
+	if table.BlockPackage != nil && table.BlockPackage.Keys != nil && len(table.BlockPackage.Keys) == 1 {
+		if table.BlockPackage.Map != nil {
+			onePkgKey := table.BlockPackage.Keys[0]
+			onePkg, _ := table.BlockPackage.Map[onePkgKey].(*u.BlockPackage)
+			if onePkg != nil && onePkg.WinnerOrder != nil && len(onePkg.WinnerOrder) == 0 {
+				onePkg.WinnerOrder = table.WinnerOrder
+				table.BlockPackage.Map[onePkgKey] = onePkg
+			}
+		}
+	}
+}
+
+//表格结果合并到父表格集中
+func (table *Table) MergerToTableresult() {
+	//对多包表格的多包值的合并处理
+	if table.BPackage {
+		table.TableResult.IsMultiPackage = true
+		for k, v := range table.BlockPackage.Map {
+			package1 := table.TableResult.PackageMap.Map[k]
+			if package1 == nil {
+				table.TableResult.PackageMap.AddKey(k, v)
+			} else {
+				bp := package1.(*u.BlockPackage)
+				if bp.TableKV == nil {
+					bp.TableKV = u.NewJobKv()
+				}
+				v1 := v.(*u.BlockPackage)
+				if v1.TableKV != nil && v1.TableKV.Kv != nil {
+					for k2, v2 := range v1.TableKV.Kv {
+						if bp.TableKV == nil {
+							bp.TableKV = u.NewJobKv()
+						}
+						if bp.TableKV.Kv[k2] == "" || (v1.TableKV.KvTag[k2] != nil && bp.TableKV.KvTag[k2] != nil && v1.TableKV.KvTag[k2].Weight > bp.TableKV.KvTag[k2].Weight) {
+							//可能会报错 assignment to entry in nil map
+							bp.TableKV.Kv[k2] = v2
+							bp.Text += fmt.Sprintf("%v:%v\n", k2, v2)
+						}
+					}
+				}
+				bp.WinnerOrder = v1.WinnerOrder
+				//table.TableResult.PackageMap.AddKey(k, v)
+			}
+		}
+		//		str := ""
+		//		for _, k := range table.TableResult.PackageMap.Keys {
+		//			v := table.TableResult.PackageMap.Map[k].(*u.BlockPackage)
+		//			str += fmt.Sprintf("包号:%s,中标人:%s,中标价:%s,预算:%s,文本:%s,排名:%v ---\t", v.Index, v.TableKV["中标单位"]+v.ColonKV["中标单位"], v.TableKV["中标金额"]+v.ColonKV["中标金额"], v.TableKV["预算"]+v.ColonKV["预算"], v.Text, v.WinnerOrder)
+		//		}
+		//		u.Debug(table, table.TableResult, str)
+	}
+	//遍历标准key到tableresult.sortkv中
+	for k, v := range table.StandKV {
+		if table.TableResult.SortKV.Map[k] == nil || table.StandKVWeight[k] > table.TableResult.SortKVWeight[k] || strings.Contains(table.Tag, "变更") {
+			v = strings.Replace(v, "__", "", -1)
+			if table.TableResult.SortKV.Map[k] == nil {
+				table.TableResult.SortKV.AddKey(k, v) //父集
+			} else {
+				table.TableResult.SortKV.ReplaceKey(k, v, k)
+			}
+			table.TableResult.SortKVWeight[k] = table.StandKVWeight[k]
+		} else if table.TableResult.SortKV.Map[k] != nil {
+			//u.Debug(k, v, table.TableResult.SortKV.Map[k], "..............")
+		}
+	}
+	//表格的块标签
+	if table.TableResult.BlockTag == "" && table.Tag != "" {
+		table.TableResult.BlockTag = table.Tag
+	}
+	if table.TableResult.WinnerOrder == nil || len(table.TableResult.WinnerOrder) == 0 {
+		table.TableResult.WinnerOrder = table.WinnerOrder
+	}
+}
+
+/**
+解析表格入口
+返回:汇总表格对象
+**/
+func AnalyTableV2(tabs []*goquery.Selection, toptype, blockTag, con string, itype int, _id interface{}) (tabres *TableResult) {
+	defer qutil.Catch()
+	//u.Debug(con)
+	if itype == 1 {
+		//修复表格
+		con = RepairCon(con)
+	}
+	//生成tableresult对象
+	tabres = NewTableResult(_id, toptype, blockTag, con, itype)
+	//可以有多个table
+	for _, table := range tabs {
+		//隐藏表格跳过
+		if IsHide(table) {
+			continue
+		}
+		tabres.GoqueryTabs = append(tabres.GoqueryTabs, table)
+	}
+	//解析表格集
+	tabres.Analy()
+	//u.Debug(tabres.SortKV.Map)
+	return
+}
+
+//开始解析表格集
+func (ts *TableResult) Analy() {
+	tabs := []*Table{}
+	for _, table := range ts.GoqueryTabs {
+		tn := NewTable(ts.Html, ts, table)
+		//核心模块
+		ts := tn.Analy()
+		for _, tab := range ts {
+			tabs = append(tabs, tab)
+		}
+		//tn.SonTables = append(tn.SonTables, tn)
+	}
+	//统一合并,考统一多表格是多包的情况---新增
+	if len(tabs) > 1 {
+		pns := map[string]string{}
+		pnarr := []string{}
+		for _, table := range tabs {
+			pn := table.StandKV["项目名称"]
+			if pn != "" && TitleReg.MatchString(pn) {
+				pnarr = append(pnarr, pn)
+				matchres := TitleReg.FindAllStringSubmatch(pn, -1)
+				if len(matchres) == 1 && len(matchres[0]) > 0 {
+					v1 := u.PackageNumberConvert(matchres[0][0])
+					pns[v1] = matchres[0][0]
+					bp := &u.BlockPackage{}
+					bp.Index = v1
+					bp.Origin = matchres[0][0]
+					bp.TableKV = u.NewJobKv()
+					for _, k := range []string{"中标金额", "中标单位", "预算", "成交状态", "项目名称", "项目编号", "采购范围"} {
+						bp.TableKV.Kv[k] = table.StandKV[k]
+					}
+					bp.WinnerOrder = table.WinnerOrder
+					if table.BlockPackage.Map[v1] == nil {
+						table.BPackage = true
+						table.BlockPackage.AddKey(v1, bp)
+					}
+				}
+			}
+		}
+		if len(tabs) == len(pns) {
+			//多个表格,每个表格都是一个分包 http://www.cxzwfw.gov.cn/info/1009/6963.htm
+			//项目名称、项目编号、采购单位、招标机构、预算
+			pname := projectnameReg.ReplaceAllString(pnarr[0], "")
+			btrue := true
+			for _, pn := range pnarr[1:] {
+				pn = projectnameReg.ReplaceAllString(pn, "")
+				//u.Debug(pn, pname)
+				if pn != pname {
+					//项目名称不一致
+					btrue = false
+					break
+				}
+			}
+			if btrue {
+				ts.SortKV.AddKey("项目名称", pname)
+				ts.SortKVWeight["项目名称"] = 100
+				for _, table := range tabs {
+					table.BPackage = true
+					//预算、中标金额、NullTxtBid成交供应商排名 中标单位 成交状态
+					if table.BlockPackage != nil && len(table.BlockPackage.Keys) == 1 {
+						bp := table.BlockPackage.Map[table.BlockPackage.Keys[0]].(*u.BlockPackage)
+						if table.TableResult.WinnerOrder != nil {
+							bp.WinnerOrder = table.WinnerOrder
+						}
+						if bp != nil && table.StandKV != nil {
+							if bp.TableKV == nil {
+								bp.TableKV = u.NewJobKv()
+							}
+							for nk, k := range []string{"中标金额", "中标单位", "预算", "成交状态", "项目名称", "项目编号", "采购范围"} {
+								bp.TableKV.Kv[k] = table.StandKV[k]
+								if nk < 4 {
+									delete(table.StandKV, k)
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	for _, table := range tabs {
+		table.MergerToTableresult()
+	}
+}
+
+//解析表格
+func (table *Table) Analy() []*Table {
+	//查找表体中的tr对象
+	trs := table.Goquery.ChildrenFiltered("tbody,thead,tfoot").ChildrenFiltered("tr")
+	if trs.Size() == 0 {
+		trs = table.Goquery.ChildrenFiltered("tr")
+	}
+	//num := 0
+	//遍历tr数组
+	trs.Each(func(n int, sel *goquery.Selection) {
+		//隐藏行不处理
+		if IsHide(sel) {
+			return
+		}
+		//遍历每行的td
+		tds := sel.ChildrenFiltered("td,th")
+		TR := NewTR(table)
+		tds.Each(func(m int, selm *goquery.Selection) {
+			//对隐藏列不处理!!!
+			if IsHide(selm) {
+				return
+			}
+			//进入每一个单元格
+			td := NewTD(selm, TR, table)
+			//num++
+			TR.AddTD(td)
+		})
+		table.AddTR(TR)
+	})
+	//重置行列
+	table.ComputeRowColSpan()
+
+	tm := []map[string]interface{}{}
+	tmk := map[string]bool{}
+	tmn := map[int]map[string]interface{}{}
+	for rownum, tr := range table.TRs {
+		if len(tr.TDs) == 1 && table.ColNum > 1 {
+			td := tr.TDs[0]
+			if td.StartCol == 0 && td.EndCol+1 == table.ColNum && len([]rune(td.Val)) > 1 && len([]rune(td.Val)) < 50 {
+				con, m1, b := CheckMultiPackage(td.Val, "")
+				if b {
+					for k, _ := range m1 {
+						numstr := u.PackageNumberConvert(k)
+						m2 := map[string]interface{}{
+							"tag": con,
+							//"num":      numstr,
+							//"numtxt":   v[0],
+							"startrow": rownum,
+						}
+						tmk[numstr] = true
+						tmn[rownum] = m2
+						tm = append(tm, m2)
+						break
+					}
+				}
+			}
+		}
+	}
+	//拆表
+	ts := []*Table{}
+	if len(tmk) > 1 && len(tmk) == len(tm) {
+		var tab1 *Table
+		for rownum, tr := range table.TRs {
+			if tab1 == nil {
+				tab1 = NewTable("", table.TableResult, table.Goquery)
+				tab1.BSplit = true
+				if tmn[rownum] != nil {
+					tab1.StandKV["项目名称"] = tmn[rownum]["tag"].(string)
+					tab1.StandKVWeight["项目名称"] = -100
+				}
+				ts = append(ts, tab1)
+			}
+			if tmn[rownum] != nil {
+				tab1.Tag = tmn[rownum]["tag"].(string)
+			} else {
+				tab1.AddTR(tr)
+			}
+			if tmn[rownum+1] != nil {
+				tab1 = nil
+			}
+		}
+	} else {
+		ts = append(ts, table)
+	}
+	for n, table := range ts {
+		if len(table.TRs) > 0 {
+			//删除尾部空行
+			for len(table.TRs) > 0 {
+				npos := len(table.TRs)
+				tailTR := table.TRs[npos-1]
+				bspace := true
+				for _, v := range tailTR.TDs {
+					if v.Val != "" || v.SonTableResult != nil || len(v.SortKV.Keys) > 0 {
+						bspace = false
+						break
+					}
+				}
+				if bspace {
+					table.TRs = table.TRs[:npos-1]
+				} else {
+					break
+				}
+			}
+			//table.Print()
+			//校对表格
+			table.Adjust()
+			//查找表格的标签
+			table.FindTag()
+			//u.Debug(table.TableResult.Id, table.Tag)
+			//分割表格
+			if table.BSplit {
+				if !table.BHeader && n > 0 {
+					for i := n - 1; i > -1; i-- {
+						if ts[i].BHeader {
+							if ts[i].BFirstRow {
+								//取第一行插入到
+								table.InsertTR(ts[i].TRs[0])
+								table.Adjust()
+							}
+							break
+						}
+					}
+				}
+			}
+			//对没有表头表格的处理
+			_, _, b := CheckMultiPackage(table.Tag, "")
+			if b {
+				table.StandKV["项目名称"] = table.Tag
+				table.StandKVWeight["项目名称"] = -100
+			}
+			//table.TdContactFormat(contactFormat)
+			//开始查找kv,核心模块
+			table.FindKV()
+			//判断是否是多包,并处理分包的
+			table.CheckMultiPackageByTable()
+			str := "\n"
+			for k, v := range table.StandKV {
+				str += fmt.Sprintf("_==___%s:%v\n", k, v)
+				if table.TableResult.SortKV.Map[k] == nil {
+					table.TableResult.SortKV.AddKey(k, v)
+					table.TableResult.SortKVWeight[k] = table.StandKVWeight[k]
+				}
+			}
+			res, _, _, _, _ := CheckCommon(table.Tag, "abandontable")
+			if !res {
+				//过滤、标准化、合并kv
+				table.KVFilter()
+			}
+			str = "\n"
+			for k, v := range table.StandKV {
+				str += fmt.Sprintf("_____%s:%v\n", k, v)
+				if table.TableResult.SortKV.Map[k] == nil {
+					table.TableResult.SortKV.AddKey(k, v)
+					table.TableResult.SortKVWeight[k] = table.StandKVWeight[k]
+				}
+			}
+			//u.Debug(str)
+		}
+
+	}
+	//	if table.BPackage {
+	//		u.Debug(table, fmt.Sprintf("%v", table.BlockPackage.Map["1"]))
+	//	}
+	return ts
+}
+
+func (table *Table) Adjust() {
+	table.TDNum = func() int {
+		n := 0
+		for _, tr := range table.TRs {
+			n += len(tr.TDs)
+		}
+		return n
+	}()
+	//有多少行
+	table.RowNum = len(table.TRs)
+	//		for k1, tr := range table.TRs {
+	//			for k2, td := range tr.TDs {
+	//				u.Debug(k1, k2, td.Val, td.Rowspan, td.Colspan, td.ColPos, tr.RowPos)
+	//			}
+	//		}
+	//计算行列起止位置,跨行跨列处理
+	table.ComputeRowColSpan()
+	//		for k1, tr := range table.TRs {
+	//			for k2, td := range tr.TDs {
+	//				u.Debug(k1, k2, td.Val, td.StartRow, td.EndRow, td.StartCol, td.EndCol)
+	//			}
+	//		}
+	//大概计算每个起止行列的概率
+	table.GetKeyRation()
+	/*
+		for k, v := range table.StartAndEndRation {
+			for k1, v1 := range v.Poss {
+				bs, _ := json.Marshal(v1)
+				str := ""
+				for _, td := range v.Tdmap[v1] {
+					str += "__" + td.Val + fmt.Sprintf("%d_%d_%d_%d", td.StartRow, td.EndRow, td.StartCol, td.EndCol)
+				}
+				u.Debug(k, k1, string(bs), v.Rationmap[v1], str)
+			}
+		}
+	*/
+	//u.Debug("tdnum:", num, table.RowNum, table.ColNum)
+	//是否是规则的表格,单元各个数=行数*列数
+	table.Brule = table.TDNum == table.RowNum*table.ColNum
+	count := 0
+	for _, trs := range table.TRs {
+		for _, td := range trs.TDs {
+			if td.BH {
+				count++
+			}
+		}
+	}
+	if float32(count)/float32(table.TDNum) < 0.85 {
+		//精确计算起止行列是表头的概率
+		table.ComputeRowColIsKeyRation()
+		bhead := false
+	L:
+		for i, tr := range table.TRs {
+			for _, td := range tr.TDs {
+				if td.BH {
+					//u.Debug("----=====---", td.Val, len(table.TRs[len(table.TRs)-1].TDs), i, len(table.TRs)-1)
+					if i == len(table.TRs)-1 && len(table.TRs[len(table.TRs)-1].TDs) == 2 {
+						res, _, _, _, _ := CheckCommon(td.Val, "abandontable")
+						if res {
+							//删除此行
+							table.TRs = table.TRs[:len(table.TRs)-1]
+							table.Adjust()
+							return
+						}
+					}
+					bhead = true
+					break L
+				}
+			}
+		}
+		table.BHeader = bhead
+	}
+}
+
+//计算行/列表格的结束位置 StartRow=0 EndRow=0
+func (table *Table) ComputeRowColSpan() {
+	mapRC := map[int]map[int]int{} //记录第几行pos,起始列对应的合并值
+	for k, v := range table.TRs {
+		nk := 0 //nk列的起始,k行的起始||如果有合并,起始就不是0
+		ball := true
+		rowspans := v.TDs[0].Rowspan
+		for _, v1 := range v.TDs {
+			if v1.Rowspan != rowspans {
+				ball = false
+				break
+			}
+		}
+		for _, v1 := range v.TDs {
+			if ball {
+				v1.Rowspan = 1
+			}
+			mc := mapRC[k]
+			for {
+				if mc != nil && mc[nk] > 0 {
+					nk += mc[nk]
+				} else {
+					break
+				}
+			}
+			v1.StartCol = nk
+			nk += v1.Colspan - 1
+			v1.EndCol = nk
+			if nk >= table.ColNum {
+				table.ColNum = nk + 1
+			}
+			nk++
+			v1.StartRow = k
+			v1.EndRow = k + v1.Rowspan - 1
+			ck := fmtkey("c", v1.StartCol, v1.EndCol)
+			tdcs := table.StartAndEndRation[ck]
+			if tdcs == nil {
+				tdcs = NewTDRationScope(ck)
+				table.StartAndEndRation[ck] = tdcs
+				table.StartAndEndRationKSort.AddKey(ck, 1)
+			}
+			tdcs.Addtd(v1)
+			rk := fmtkey("r", v1.StartRow, v1.EndRow)
+			tdrs := table.StartAndEndRation[rk]
+			if tdrs == nil {
+				tdrs = NewTDRationScope(rk)
+				table.StartAndEndRation[rk] = tdrs
+				table.StartAndEndRationKSort.AddKey(rk, 1)
+			}
+			tdrs.Addtd(v1)
+			if v1.Rowspan > 1 {
+				for i := 1; i < v1.Rowspan; i++ {
+					r := k + i
+					if r < len(table.TRs) {
+						mc := mapRC[r]
+						if mc == nil {
+							mc = map[int]int{}
+						}
+						mc[v1.StartCol] = v1.Colspan
+						mapRC[r] = mc
+					}
+				}
+			}
+		}
+	}
+}
+
+func fmtkey(t string, start, end int) string {
+	return fmt.Sprintf("%s_%d_%d", t, start, end)
+}
+
+func (table *Table) FindTag() {
+	//查找每个table的标签,如果有标签可按标签处理,否则根据表格去判断
+	if table.Tag != "" {
+		return
+	}
+	t1, _ := table.Goquery.OuterHtml()
+	html := table.Html
+	pos := strings.Index(html, t1)
+	if pos <= 0 {
+		doc, _ := goquery.NewDocumentFromReader(strings.NewReader(table.Html))
+		html, _ = doc.Html()
+		pos = strings.Index(html, t1)
+	}
+	//u.Debug("--------", t1, "====\n\n\n\n=====", html)
+	if pos > 0 {
+		tcon := html[:pos]
+		tcon = cut.ClearHtml(tcon)
+		tcon = ClearTagReg.ReplaceAllString(tcon, "")
+		//u.Debug(pos, "-----------", tcon)
+		strs := ttagreg.FindStringSubmatch(tcon)
+		if len(strs) > 0 {
+			table.Tag = strs[0]
+			//u.Debug(table.Tag)
+		}
+	}
+	if table.Tag == "" {
+		table.Tag = table.TableResult.BlockTag
+	}
+	//u.Debug(table.Tag)
+}
+
+//计算r/c_start_end的概率
+func (table *Table) GetKeyRation() {
+	for _, vn := range table.StartAndEndRationKSort.Keys {
+		v := table.StartAndEndRation[vn]
+		for _, v1 := range v.Poss {
+			count := 0
+			n := 0
+			for _, td := range v.Tdmap[v1] {
+				n++
+				if td.BH {
+					count++
+				}
+			}
+			v.Rationmap[v1] = float32(count) / float32(n)
+		}
+	}
+}
+
+//计算行列是表头的概率调用GetKeyRation
+func (table *Table) ComputeRowColIsKeyRation() {
+	//增加对跨行校正限止
+	//	u.Debug(table.Brule, table.ColNum, table.RowNum, table.TDNum)
+	bkeyfirstrow := false
+	bkeyfirstcol := false
+	if table.Brule { //不存在跨行跨列的情况,规则表格
+		checkCompute := map[string]bool{}
+		for k, tr := range table.TRs {
+			rk := fmtkey("r", tr.TDs[0].StartRow, tr.TDs[0].EndRow)
+			if k == 0 { //第1行的概率
+				ck := fmtkey("c", tr.TDs[0].StartCol, tr.TDs[0].EndCol)
+				//u.Debug(table.BFirstRow, "--", table.StartAndEndRation[rk], table.StartAndEndRation[ck])
+				ration1, _ := table.StartAndEndRation[rk].GetTDRation(tr.TDs[0])
+				ration2, _ := table.StartAndEndRation[ck].GetTDRation(tr.TDs[0])
+				if (len(tr.TDs) == 2 && ration2 < 0.55) && (len(tr.TDs) == 2 && ration1 > 0.5) { //第一行为key
+					bkeyfirstrow = true
+					ball := true
+					for _, td := range tr.TDs {
+						if MoneyReg.MatchString(td.Val) {
+							bkeyfirstrow = false
+							ball = false
+							td.BH = false
+							break
+						}
+					}
+					for _, td := range tr.TDs {
+						if ball {
+							td.BH = true
+							td.KeyDirect = 1
+							td.KVDirect = 2
+						}
+					}
+				} else if ration2 > 0.55 { //第1列
+					bkeyfirstcol = true
+					if !checkCompute[ck] {
+						checkCompute[ck] = true
+						//重置第1列
+						for _, tr1 := range table.TRs {
+							for _, td1 := range tr1.TDs {
+								if td1.StartCol == 0 {
+									if !MoneyReg.MatchString(td1.Val) {
+										td1.BH = true
+										td1.KeyDirect = 2
+										td1.KVDirect = 1
+									}
+								}
+							}
+						}
+					}
+				}
+				if !bkeyfirstrow && !bkeyfirstcol {
+					if len(tr.TDs) > 1 && ration1 > ration2 && ration1 > 0.5 {
+						bkeyfirstrow = true
+						for _, td := range tr.TDs {
+							if !MoneyReg.MatchString(td.Val) {
+								td.BH = true
+								td.KeyDirect = 1
+								td.KVDirect = 2
+							}
+						}
+					} else if tr.Table.ColNum > 1 && ration2 > 0.5 {
+						bkeyfirstcol = true
+						if !checkCompute[ck] {
+							checkCompute[ck] = true
+							//重置第1列
+							for _, tr1 := range table.TRs {
+								for _, td1 := range tr1.TDs {
+									if td1.StartCol == 0 {
+										if !MoneyReg.MatchString(td1.Val) {
+											td1.BH = true
+											td1.KeyDirect = 2
+											td1.KVDirect = 1
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			} else {
+				if bkeyfirstrow {
+					//第一列的概率
+					ration1, _ := table.StartAndEndRation[rk].GetTDRation(tr.TDs[0])
+					if k == 1 || ration1 < checkval {
+						for _, td := range tr.TDs {
+							if !td.MustBH {
+								td.BH = false
+								td.KeyDirect = 0
+								td.KVDirect = 0
+							}
+						}
+					} //else {for _, td := range tr.TDs {}}
+				} else {
+					//列在起作用
+					if bkeyfirstcol {
+						for _, td := range tr.TDs {
+							ck := fmtkey("c", td.StartCol, td.EndCol)
+							ration1, _ := table.StartAndEndRation[ck].GetTDRation(td)
+							if !checkCompute[ck] {
+								checkCompute[ck] = true
+								if ration1 >= checkval && td.ColPos != 1 {
+									for _, tr1 := range table.TRs {
+										for _, td1 := range tr1.TDs {
+											if td1.StartCol == td.StartCol {
+												if !MoneyReg.MatchString(td1.Val) {
+													td1.BH = true
+													td1.KeyDirect = 2
+													td1.KVDirect = 1
+												}
+											}
+										}
+									}
+								} else {
+									for _, tr1 := range table.TRs[1:] {
+										for _, td1 := range tr1.TDs[1:] {
+											if td1.StartCol == td.StartCol && !td1.MustBH {
+												td1.BH = false
+												td1.KeyDirect = 0
+												td1.KVDirect = 0
+											}
+										}
+									}
+								}
+							}
+
+						}
+					}
+				}
+			}
+		}
+	}
+	if !table.Brule || (!bkeyfirstcol && !bkeyfirstrow) {
+		//断行问题,虽然同列或同行,但中间被跨行截断,表格方向调整
+		for _, k := range table.StartAndEndRationKSort.Keys {
+			v := table.StartAndEndRation[k]
+			//横向判断,要判断最多的方向,否则会出现不定的情况(map遍历问题)
+			k1 := k[:1]
+			for _, v2 := range v.Poss {
+				lentds := len(v.Tdmap[v2])
+				if v.Rationmap[v2] > checkval {
+					for _, td := range v.Tdmap[v2] {
+						if td.KeyDirect == 0 && !MoneyReg.MatchString(td.Val) {
+							if k1 == "r" {
+								ck := fmtkey("c", td.StartCol, td.EndCol)
+								rt := table.StartAndEndRation[ck]
+								//clen := 0
+								var fv float32
+								var tdn []*TD
+								if rt != nil {
+									fv, tdn = rt.GetTDRation(td)
+									//clen = len(tdn)
+								}
+								if lentds > 1 {
+									if ((tdn != nil && v.Rationmap[v2] > fv) || tdn == nil) && td.Valtype != "BO" {
+										td.KeyDirect = 1
+										td.KVDirect = 2
+										td.BH = true
+									}
+								}
+							} else {
+								ck := fmtkey("r", td.StartRow, td.EndRow)
+								rt := table.StartAndEndRation[ck]
+								var fv float32
+								var tdn []*TD
+								//clen := 0
+								if rt != nil {
+									fv, tdn = rt.GetTDRation(td)
+									//clen = len(tdn)
+								}
+								if lentds > 1 {
+									if ((tdn != nil && v.Rationmap[v2] > fv) || tdn == nil) && td.Valtype != "BO" {
+										td.KeyDirect = 2
+										td.KVDirect = 1
+										td.BH = true
+									}
+								}
+							}
+
+						} else {
+							break
+						}
+					}
+				} else if v.Rationmap[v2] < 0.5 && len(v.Tdmap[v2]) > 3 {
+					for _, td := range v.Tdmap[v2] {
+						//						u.Debug(td.Val, "-----", td.BH)
+						if td.KeyDirect == 0 && td.BH && !td.MustBH {
+							if k1 == "r" {
+								ck := fmtkey("c", td.StartCol, td.EndCol)
+								rt := table.StartAndEndRation[ck]
+								clen := 0
+								var fv float32
+								var tdn []*TD
+								if rt != nil {
+									fv, tdn = rt.GetTDRation(td)
+									clen = len(tdn)
+								}
+								if lentds >= clen && lentds > 1 {
+									if (tdn != nil && v.Rationmap[v2] < fv) || tdn == nil {
+										td.BH = false
+									}
+								}
+							} else {
+								ck := fmtkey("r", td.StartRow, td.EndRow)
+								rt := table.StartAndEndRation[ck]
+								var fv float32
+								var tdn []*TD
+								clen := 0
+								if rt != nil {
+									fv, tdn = rt.GetTDRation(td)
+									clen = len(tdn)
+								}
+								if lentds >= clen && lentds > 1 {
+									if (tdn != nil && v.Rationmap[v2] < fv) || tdn == nil {
+										td.BH = false
+									}
+								}
+							}
+
+						} else {
+							break
+						}
+					}
+				}
+			}
+		}
+	}
+	table.GetKeyRation()
+	if len(table.TRs) > 0 && len(table.TRs[0].TDs) > 0 {
+		t0 := table.TRs[0].TDs[0]
+		key := fmtkey("r", t0.StartRow, t0.EndRow)
+		r, t := table.StartAndEndRation[key].GetTDRation(t0)
+		if r > 0.9 && len(t) > 1 {
+			table.BFirstRow = true
+		}
+		for k, tr := range table.TRs {
+			if len(tr.TDs) == 1 && tr.TDs[0].StartCol == 0 && tr.TDs[0].EndCol+1 == table.ColNum {
+				tr.TDs[0].BH = false
+				tr.TDs[0].KVDirect = 0
+				sv := FindKv(tr.TDs[0].Val, "", 2)
+				_, resm := colonkvEntity.entrance(tr.TDs[0].Val, "", 2)
+				for k, v := range resm {
+					sv.AddKey(k, v)
+				}
+				if len(sv.Keys) > 0 {
+					for k1, v1 := range sv.Map {
+						if tr.TDs[0].SortKV.Map[k1] == nil {
+							table.SortKV.AddKey(k1, v1)
+						}
+					}
+				} else if table.Tag == "" && k == 0 && len(tr.TDs[0].Val) > 11 {
+					table.Tag = tr.TDs[0].Val
+				}
+
+				//			subVal := tdval_reg.FindAllStringSubmatch(tr.TDs[0].Val, -1)
+				//			//u.Debug(tr.TDs[0].Val, subVal)
+				//			if len(subVal) > 0 {
+				//				for _, subv1 := range subVal {
+				//					if len(subv1) == 3 {
+				//						table.SortKV.AddKey(subv1[1], subv1[2])
+				//					}
+				//				}
+				//			} else if k == 0 && len(tr.TDs[0].Val) > 11 {
+				//				table.Tag = tr.TDs[0].Val
+				//			}
+
+			}
+			//		for _, td := range tr.TDs {
+			//			u.Debug(td.BH, td.Val, "----")
+			//		}
+		}
+	}
+}
+
+//查找表格的kv,调用FindTdVal
+func (table *Table) FindKV() {
+	//判断全是key的表格不再查找
+	if table.BHeader { //只要一个是key即为true
+		direct := If(table.BFirstRow, 2, 1).(int) //kv,2查找方向,向上查找
+		vdirect := If(direct == 2, 1, 2).(int)
+		//控制跨行表格
+		bcon := false
+		//增加表格切块判断,只判断切块分包
+		//控制中标人排序方向
+		bodirect := 0
+		//控制中标人排序数值
+		sort := 1
+		//开始抽取
+		for _, tr := range table.TRs {
+			if len(tr.TDs) == 1 {
+				bcon = false
+				td := tr.TDs[0]
+				if td.StartCol == 0 && td.EndCol+1 == table.ColNum && len([]rune(td.Val)) > 4 && len([]rune(td.Val)) < 50 {
+					res, _, _, _, _ := CheckCommon(td.Val, "abandontable")
+					if res { //以下内容丢弃
+						bcon = true
+					}
+				}
+			}
+			if bcon {
+				continue
+			}
+			for _, td := range tr.TDs {
+				//u.Debug(td.Val, td.BH, td.StartRow, td.StartCol)
+				//u.Debug(td.BH, td.Val)
+				/**
+				rt := table.StartAndEndRation[fmtkey("r", td.StartCol, td.EndCol)]
+				if rt != nil {
+					r, t := rt.GetTDRation(td)
+					u.Debug(td.BH, td.Val, r, t)
+				}
+				**/
+				if !td.BH && td.KVDirect < 3 {
+					if !table.FindTdVal(td, direct, vdirect) {
+						if !table.FindTdVal(td, vdirect, direct) {
+							//都识别不到时,对第一、二中标候选人的处理
+							bo, res := GetBidOrder(td, bodirect, sort)
+							if res {
+								sort++
+								bodirect = bo
+							}
+						}
+					}
+					//u.Debug(td.Val, td.BH, td.HeadTd, td.KVDirect)
+				}
+
+			}
+		}
+	} else if len(table.TRs) > 0 { //没有表头的表格处理,默认纵向吧
+		res := make([][]string, len(table.TRs[0].TDs))
+		for n, _ := range res {
+			res[n] = []string{}
+		}
+		for _, tr := range table.TRs {
+			for n, td := range table.TRs[0].TDs { //第一行的所有td
+				td1 := table.GetTdByRCNo(tr.TDs[0].StartRow, td.StartCol)
+				if td1 != nil {
+					res[n] = append(res[n], td1.Val)
+				} else {
+					res[n] = append(res[n], "")
+				}
+			}
+		}
+		//再拆值,类似http://www.ggzy.hi.gov.cn/cgzbgg/16553.jhtml第二列,有多个值
+		nmapkeys := []int{}
+		nmap := map[int][]*u.Kv{}
+	L:
+		for _, r1 := range res {
+			for n, r := range r1 {
+				if len([]rune(r)) < 60 { // 长度小于60才去分
+					//res1, _ := GetKVAll(r, "", nil)
+					res1, _ := colonkvEntity.entrance(r, "", 2)
+					if res1 != nil {
+						nmap[n] = res1
+						nmapkeys = append(nmapkeys, n)
+						/**
+						//截取串
+						for _k1, _ := range res1 {
+							r = regexp.MustCompile(_k1+".*").ReplaceAllString(r, "")
+						}
+						r1[n] = r
+						res[pos] = r1
+						**/
+					} else if nmap[n] != nil {
+						//放空值
+						nmap[n] = append(nmap[n], &u.Kv{})
+					}
+				} else {
+					nmap = nil
+					nmapkeys = nil
+					break L
+				}
+			}
+		}
+		//调整
+		if len(nmap) > 0 {
+			kmapkeys := []string{}
+			kmap := map[string][]string{}
+			for _, mk := range nmapkeys { //同是第n列
+				for pos, m1 := range nmap[mk] {
+					k, v := m1.Key, m1.Value
+					kv := kmap[k]
+					if kv == nil {
+						kv = []string{}
+					}
+					kv = append(kv, v)
+					kmap[k] = kv
+					kmapkeys = append(kmapkeys, k)
+					for _, k := range kmapkeys {
+						arr := kmap[k]
+						if len(arr) < pos {
+							arr = append(arr, "")
+							kmap[k] = arr
+							kmapkeys = append(kmapkeys, k)
+						}
+					}
+				}
+			}
+			if len(kmap) > 0 {
+				for _, k := range kmapkeys {
+					table.SortKV.AddKey(k, kmap[k])
+				}
+			}
+		}
+		//=================
+		//解析值放到map中
+		for _, arr := range res {
+			if len(arr) > 0 {
+				v1 := arr[0]
+				_, _, _, _, repl := CheckCommon(v1, "con")
+				if repl == "ENT" {
+					table.SortKV.AddKey("中标单位", arr)
+					continue
+				} else if repl == "BO" {
+					table.SortKV.AddKey("排名", arr)
+					continue
+				}
+			}
+		}
+	}
+}
+
+//获取中标人顺序
+//direct 0默认 1横向 2纵向
+func GetBidOrder(td *TD, direct, n int) (d int, res bool) {
+	if td.Valtype == "BO" {
+		rtd := td.TR.Table.GetTdByRCNo(td.StartRow, td.EndCol+1)
+		btd := td.TR.Table.GetTdByRCNo(td.EndRow+1, td.StartCol)
+		if ((rtd != nil && !rtd.BH && rtd.Valtype == "BO") || direct == 1) && btd != nil && filter_zbdw_v.MatchString(btd.Val) {
+			d = 1
+			arrbo := td.TR.Table.SortKV.Map[NullTxtBid]
+			if arrbo == nil {
+				arrbo = []map[string]interface{}{}
+				td.TR.Table.SortKV.AddKey(NullTxtBid, arrbo)
+			}
+			a1 := arrbo.([]map[string]interface{})
+			a1 = append(a1, map[string]interface{}{
+				"entname": btd.Val,
+				"sortstr": td.Val,
+				"sort":    GetBidSort(td.Val, n),
+			})
+			res = true
+			td.TR.Table.SortKV.Map[NullTxtBid] = a1
+		} else if ((btd != nil && !btd.BH && btd.Valtype == "BO") || direct == 2) && rtd != nil && filter_zbdw_v.MatchString(rtd.Val) {
+			d = 2
+			arrbo := td.TR.Table.SortKV.Map[NullTxtBid]
+			if arrbo == nil {
+				arrbo = []map[string]interface{}{}
+				td.TR.Table.SortKV.AddKey(NullTxtBid, arrbo)
+			}
+			a1 := arrbo.([]map[string]interface{})
+			a1 = append(a1, map[string]interface{}{
+				"entname": rtd.Val,
+				"sortstr": td.Val,
+				"sort":    GetBidSort(td.Val, n),
+			})
+			res = true
+			td.TR.Table.SortKV.Map[NullTxtBid] = a1
+		}
+	}
+	return
+}
+
+func GetBidSort(str string, n int) int {
+	val := n
+	if strings.Index(str, "首选") > -1 {
+		val = 1
+	} else {
+		val = winnerOrderEntity.toNumber(str, n)
+	}
+	return val
+}
+
+//查找每一个单元格的表头,调用FindNear
+func (table *Table) FindTdVal(td *TD, direct, vdirect int) (b bool) {
+	near := table.FindNear(td, direct)
+	//u.Debug(td.Val, near, direct)
+	//	if near != nil {
+	//		u.Debug(td.Val, near.Val)
+	//	}
+	if near != nil && near.BH && (near.KeyDirect == vdirect || near.KeyDirect == 0) && (near.KVDirect == direct || near.KVDirect == 0) && near.KVDirect < 3 {
+		near.KVDirect = direct
+		near.KeyDirect = vdirect
+		//u.Debug(direct, near.KVDirect, near.Val, td.Val)
+		td.KVDirect = direct
+		key := near.Val
+		if near.Val == "" {
+			key = fmtkey("k", near.TR.RowPos, near.ColPos)
+		}
+		val := table.SortKV.Map[key]
+		bthiskey := false
+		if val != nil {
+			curpos := table.SortKV.Index[key]
+			thistr := table.kTD[curpos]
+			//u.Debug(curpos, key, thistr == near)
+			if thistr != near {
+				near.Val += "_"
+				for table.SortKV.Map[near.Val] != nil {
+					near.Val += "_"
+				}
+				key = near.Val //之前这个地方没有重置,导致把之前结果覆盖了
+			} else {
+				bthiskey = true
+			}
+		}
+		bfind := false
+		barr := false
+		varrpos := -1
+		if bthiskey {
+			//处理是数组值,且有合并行或合并列的情况 kvscope,对数组值的处理
+			pos := table.SortKV.Index[key]
+			mval := table.kvscope[pos]
+			bvalfind := false
+			if direct == 1 { //kv是横向
+			L1:
+				for k3, v3 := range mval {
+					for _, v4 := range v3 {
+						if v4.EndRow+1 == td.StartRow && v4.EndCol == td.EndCol {
+							varrpos = k3
+							bvalfind = true
+							break L1
+						}
+					}
+				}
+			} else { //kv是纵向
+			L2:
+				for k3, v3 := range mval {
+					for _, v4 := range v3 {
+						if v4.EndCol+1 == td.StartCol && v4.EndRow == td.EndRow {
+							varrpos = k3
+							bvalfind = true
+							break L2
+						}
+					}
+				}
+			}
+
+			if vals, ok := val.([]string); ok {
+				if near.Val == "" {
+					bn := false
+					for _, vs := range vals {
+						if vs != "" && NullTdReg.MatchString(vs) {
+							bn = true
+						} else {
+							bn = false
+							break
+						}
+					}
+					if bn {
+						near.Val = NullTxtBid
+						key = NullTxtBid
+						bfind = true
+					}
+				}
+				if bvalfind {
+					vals[varrpos] = td.Val // += "__" + td.Val
+				} else {
+					vals = append(vals, td.Val)
+					val = vals
+					varrpos = len(vals) - 1
+				}
+			} else if vals, ok := val.(string); ok {
+				if bvalfind {
+					val = td.Val //vals + "__" + td.Val
+				} else {
+					tval := []string{vals}
+					tval = append(tval, td.Val)
+					val = tval
+					varrpos = 1
+				}
+			}
+			barr = true
+		} else {
+			val = td.Val
+		}
+		td.HeadTd = near
+		if bfind {
+			tkey := fmtkey("k", near.TR.RowPos, near.ColPos)
+			table.SortKV.ReplaceKey(key, val, tkey)
+		} else {
+			//u.Debug(near.Val, td.Val, val)
+			table.SortKV.AddKey(key, val)
+			pos := table.SortKV.Index[key]
+			if barr {
+				mval := table.kvscope[pos]
+				if mval != nil {
+					tds := mval[varrpos]
+					if tds != nil {
+						tds = append(tds, td)
+					} else {
+						tds = []*TD{td}
+					}
+					if varrpos > -1 {
+						mval[varrpos] = tds
+						table.kvscope[pos] = mval
+					}
+				}
+			} else {
+				table.kvscope[pos] = map[int][]*TD{
+					0: []*TD{td},
+				}
+				table.kTD[pos] = near
+			}
+
+		}
+		b = true
+	}
+	//	 else {
+	//		u.Debug(direct, near == nil, td.Val, td.StartRow, td.EndRow, td.StartCol)
+	//		if near != nil {
+	//			u.Debug(near.Val, near.BH, near.StartRow, near.EndRow, near.KVDirect, near.KeyDirect)
+	//		}
+	//	}
+	return
+}
+
+//查找单元格的表头时,横向或纵向
+func (table *Table) FindNear(td *TD, direct int) *TD {
+	if direct == 1 && td.StartCol > 0 { //左临
+		tr := table.TRs[:td.TR.RowPos+1]
+		for i := len(tr) - 1; i > -1; i-- {
+			tds := tr[i].TDs
+			for _, td1 := range tds {
+				if td1.StartRow <= td.StartRow && td1.EndRow >= td.EndRow && td1.EndCol+1 == td.StartCol {
+					//找到左临节点
+					if td1.BH {
+						return td1
+					} else {
+						if td1.HeadTd != nil && td1.HeadTd.KVDirect == direct {
+							return td1.HeadTd
+						}
+					}
+				}
+			}
+		}
+	} else if direct == 2 && td.StartRow > 0 { //上临
+		tr := table.TRs[:td.TR.RowPos]
+		for i := len(tr) - 1; i > -1; i-- {
+			tds := tr[i].TDs
+			for _, td1 := range tds {
+				if td1.StartCol <= td.StartCol && td1.EndCol >= td.EndCol && td1.EndRow+1 == td.StartRow {
+					//找到左临节点
+					if td1.BH {
+						return td1
+					} else {
+						if td1.HeadTd != nil && td1.HeadTd.KVDirect == direct {
+							return td1.HeadTd
+						}
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+//根据行号列号获取td对象
+func (tn *Table) GetTdByRCNo(row, col int) *TD {
+	for _, tr := range tn.TRs {
+		for _, td := range tr.TDs {
+			if td.StartCol <= col && td.EndCol >= col && td.StartRow <= row && td.EndRow >= row {
+				return td
+			}
+		}
+	}
+	return nil
+}
+
+//判断表格是否是分包
+func (tn *Table) CheckMultiPackageByTable() (b bool, index []string) {
+	pac := 0
+	val := 0
+	index = []string{}
+	index_pos := []int{}
+	//是数组且能找到标段之类的提示
+	arr_count := 0
+	key_index := -1
+	//	for k, v := range tn.SortKV.Map["成交供应商"].([]string) {
+	//		u.Debug(k, v)
+	//	}
+	hasPkgTd := map[string]bool{}
+	for in, k := range tn.SortKV.Keys {
+		if excludeKey.MatchString(BracketsTextReg.ReplaceAllString(k, "")) {
+			continue
+		}
+		v := tn.SortKV.Map[k]
+		vs, bvs := v.([]string)
+		if bvs {
+			arr_count++
+			haspkgs := []string{}
+			for in2, v1 := range vs {
+				v1 = replPkgConfusion(v1)
+				if len([]rune(v1)) < 8 && !moneyNum.MatchString(v1) && FindVal_1.MatchString(v1) {
+					if key_index == -1 {
+						key_index = in
+					} else if key_index != in {
+						break
+					}
+					index = append(index, FindVal_1.FindString(v1))
+					index_pos = append(index_pos, in2)
+					val += 1
+					pac++
+				} else {
+					if ok, v1new := isHasOnePkgAndNoKv(v1); ok {
+						haspkgs = append(haspkgs, v1new)
+					}
+				}
+			}
+			/*处理这种情况:
+			<tr><td>包一:xxxxxxxxx</td></tr>
+			<tr><td>包二:xxxxxxxxx</td></tr>
+			*/
+			if len(index) == 0 && len(haspkgs) > 0 && len(haspkgs) == len(vs) {
+				for in2, v1 := range haspkgs {
+					if key_index == -1 {
+						key_index = in
+					} else if key_index != in {
+						break
+					}
+					index = append(index, v1)
+					index_pos = append(index_pos, in2)
+					val += 1
+					pac++
+				}
+			}
+		} else if v1, ok := v.(string); ok {
+			v1 = replPkgConfusion(v1)
+			if len([]rune(v1)) < 8 && !moneyNum.MatchString(v1) && FindVal_1.MatchString(v1) {
+				key_index = in
+				index = append(index, FindVal_1.FindString(v1))
+				index_pos = append(index_pos, 0)
+				val += 1
+				pac++
+			} else if getTd := tn.GetTdByRCNo(0, tn.SortKV.Index[k]); getTd != nil && getTd.KVDirect == 2 {
+				/*处理这种情况:
+				<tr><td>包一:xxxxxxxxx</td></tr>
+				*/
+				if ok, v1new := isHasOnePkgAndNoKv(v1); ok {
+					hasPkgTd[k] = true
+					key_index = in
+					index = append(index, v1new)
+					index_pos = append(index_pos, 0)
+					val += 1
+					pac++
+				}
+			}
+		}
+	}
+	//key是分包的情况
+	//记录key对应的值
+	commonKeyVals := map[string][]string{}
+	//记录key出现的次数
+	keyExistsCount := map[string]int{}
+	if pac > 1 {
+		val = 10
+	} else {
+		//查找标签
+		if TableMultiPackageReg_4.MatchString(tn.Tag) {
+			val += 4
+		} else if TableMultiPackageReg_2.MatchString(tn.Tag) {
+			val += 4
+		}
+		keyIsPkg := false
+		for in, k := range tn.SortKV.Keys {
+			if excludeKey.MatchString(BracketsTextReg.ReplaceAllString(k, "")) {
+				continue
+			}
+			v := tn.SortKV.Map[k]
+			//key是分包的情况
+			if ismatch := FindVal_1.MatchString(k); keyIsPkg || ismatch {
+				if ismatch {
+					keyIsPkg = true
+					val += 4
+					pkgFlag := FindVal_1.FindString(k)
+					k = strings.Replace(k, pkgFlag, "", -1)
+					index = append(index, pkgFlag)
+					index_pos = append(index_pos, len(index))
+					val += 1
+					pac++
+				} else {
+					k = strings.TrimRight(k, "_")
+				}
+				keyExistsCount[k] = keyExistsCount[k] + 1
+				commonKeyVals[k] = append(commonKeyVals[k], qutil.ObjToString(v))
+			} else if k1 := FilterKey_2.ReplaceAllString(k, ""); FindKey_2.MatchString(k1) {
+				val += 4
+				if vs, bvs1 := v.([]string); bvs1 {
+				L:
+					for in2, v1 := range vs {
+						if len([]rune(v1)) < 20 && !moneyNum.MatchString(v1) && FindVal2_1.MatchString(v1) {
+							for _, serial := range regSerialTitles_2 {
+								if serial.MatchString(v1) {
+									break L
+								}
+							}
+							if key_index == -1 {
+								key_index = in
+							} else if key_index != in {
+								break
+							}
+							index = append(index, v1)
+							index_pos = append(index_pos, in2)
+							val += 1
+							pac++
+						}
+					}
+				} else if v1, ok := v.(string); ok && !hasPkgTd[k] {
+					v1 = replPkgConfusion(v1)
+					for _, v2 := range strings.Split(v1, "/") {
+						if len([]rune(v2)) < 20 && !moneyNum.MatchString(v2) && FindVal2_1.MatchString(v2) {
+							key_index = in
+							index = append(index, v1)
+							index_pos = append(index_pos, 0)
+							val += 1
+							pac++
+							underline := ""
+							for {
+								underline += "_"
+								if tn.SortKV.Map[k+underline] == nil {
+									break
+								} else if v3, v2_ok := tn.SortKV.Map[k+underline].(string); v2_ok && v3 != "" {
+									index = append(index, v3)
+									index_pos = append(index_pos, 1)
+								} else if v3, v2_ok := tn.SortKV.Map[k+underline].([]string); v2_ok {
+									for v2_k, v2_v := range v3 {
+										index = append(index, v2_v)
+										index_pos = append(index_pos, v2_k+1)
+									}
+								}
+							}
+							break
+						}
+					}
+				}
+				break
+			}
+		}
+	}
+	//	u.Debug(tn.SortKV.Keys)
+	//	u.Debug(tn.SortKV.Map)
+	//	u.Debug(index)
+	//过滤重复及标准化!
+	standIndex := []string{}
+	standIndex_pos := []int{}
+	oldIndex := []string{}
+	brepeat := map[string]bool{}
+	for k, v := range index {
+		v = u.PackageNumberConvert(v)
+		if !brepeat[v] {
+			brepeat[v] = true
+			standIndex = append(standIndex, v)
+			standIndex_pos = append(standIndex_pos, index_pos[k])
+			oldIndex = append(oldIndex, v)
+		}
+	}
+	index = standIndex
+	//有一个以上的包,并且相同的key出现一次以上,认为这个key是属于包里面的
+	if len(commonKeyVals) > 0 {
+		for k, v := range commonKeyVals {
+			if len(index) > 1 && keyExistsCount[k] < 2 {
+				continue
+			}
+			tn.SortKV.AddKey(k, v)
+		}
+	}
+	//
+	isGoonNext := false
+	if val > 4 && len(brepeat) > 0 {
+		b = true
+		//多包解析
+		if b {
+			tn.BPackage = true
+			for nk, v := range index {
+				if tn.BlockPackage.Map[v] == nil {
+					bp := &u.BlockPackage{}
+					bp.Index = v
+					bp.Origin = oldIndex[nk]
+					bp.TableKV = u.NewJobKv()
+					tn.BlockPackage.AddKey(v, bp)
+				}
+			}
+			if len(index) == 1 { //是一个的情况
+				if len(tn.SortKV.Keys) < 10 && tn.ColNum < 10 && tn.RowNum < 4 {
+					beq := true
+					for _, v2 := range tn.SortKV.Map {
+						if _, ok := v2.(string); !ok {
+							beq = false
+							break
+						}
+					}
+					if beq { //统一处理为数组
+						td := tn.GetTdByRCNo(tn.RowNum-1, 0)
+						if !td.BH && FindVal2_1.MatchString(td.Val) {
+							for k2, v2 := range tn.SortKV.Map {
+								tn.SortKV.Map[k2] = []string{v2.(string)}
+							}
+						} else {
+							//没有处理成数组的情况下,继续调用正文查找分包的方法
+							isGoonNext = true
+						}
+					}
+				}
+			}
+			for _, k1 := range tn.SortKV.Keys {
+				v1 := tn.SortKV.Map[k1]
+				if _, bvs := v1.(string); bvs && len(index) > 1 && !strings.HasSuffix(k1, "_") {
+					v1_array := []string{v1.(string)}
+					underline := ""
+					for {
+						underline += "_"
+						if tn.SortKV.Map[k1+underline] == nil {
+							break
+						} else if v3, v2_ok := tn.SortKV.Map[k1+underline].(string); v2_ok && v3 != "" {
+							v1_array = append(v1_array, v3)
+						}
+					}
+					v1 = v1_array
+				}
+				if val, bvs := v1.([]string); bvs {
+					if len(val) <= len(index) {
+						for k, v := range val {
+							tn.assemblePackage(k1, v, index[k])
+						}
+					} else {
+						for sk1, sv2 := range index {
+							v := val[sk1]
+							//处理http://www.hljcg.gov.cn/xwzs!queryOneXwxxqx.action?xwbh=8145b599-a11e-45cb-a76a-12157a715570
+							if v == "" && strings.Index(k1, "供应商") > -1 {
+								if sk1 != len(index)-1 {
+									//u.Debug(val[sk1+1], val[sk1+2])
+									if standIndex_pos[sk1+1]-standIndex_pos[sk1] > 1 {
+										v = val[standIndex_pos[sk1]+1]
+									}
+								} else {
+									if standIndex_pos[sk1] < len(val)-1 {
+										v = val[standIndex_pos[sk1]+1]
+									}
+								}
+							}
+							tn.assemblePackage(k1, v, sv2)
+						}
+					}
+					//删除子包的kv
+					//u.Debug("----==1==-------", k1)
+					k1tags := u.GetTags(k1)
+					if !(len(k1tags) > 0 && k1tags[0].Value == "采购单位") {
+						tn.SortKV.RemoveKey(k1)
+					}
+				} else if val, bvs := v1.(string); bvs && len(index) == 1 {
+					//删除子包的kv
+					k1tags, _, _, _, _ := CommonDataAnaly(k1, "", "", val)
+					//k1tags := u.GetTags(k1)
+					if !(len(k1tags) > 0 && regexp.MustCompile("^(项目|开标|采购单位|招标机构)").MatchString(k1tags[0])) { //(k1tags[0].Value == "采购单位" || k1tags[0].Value == "项目编号")) {
+						//u.Debug(k1, val)
+						tn.assemblePackage(k1, val, index[0])
+						tn.SortKV.RemoveKey(k1)
+					}
+					//u.Debug("----==2==-------", k1)
+				}
+			}
+		}
+	} else {
+		isGoonNext = true
+	}
+	if isGoonNext {
+		blockPackage := map[string]*u.BlockPackage{}
+		for _, k := range tn.SortKV.Keys {
+			if excludeKey.MatchString(k) {
+				continue
+			}
+			str := ""
+			v := tn.SortKV.Map[k]
+			nk := regReplAllSpace.ReplaceAllString(k, "")
+			if vs, ok := v.([]string); ok {
+				str += fmt.Sprintf("%s:%s\n", nk, strings.Join(vs, " "))
+			} else {
+				str += fmt.Sprintf("%s:%s\n", nk, v)
+			}
+			b, _ := divisionPackageChild(&blockPackage, str, tn.Tag, false, false)
+			if b && len(blockPackage) > 0 {
+				tn.BPackage = true
+				for mk, mv := range blockPackage {
+					if tn.BlockPackage.Map[mk] == nil {
+						tn.BlockPackage.AddKey(mk, mv)
+					} else {
+						bp := tn.BlockPackage.Map[mk].(*u.BlockPackage)
+						if bp.TableKV == nil {
+							bp.TableKV = u.NewJobKv()
+						}
+						for k2, v2 := range mv.ColonKV.Kv {
+							if bp.TableKV.Kv[k2] == "" {
+								bp.TableKV.Kv[k2] = v2
+								bp.TableKV.KvTag[k2] = mv.ColonKV.KvTag[k2]
+								bp.Text += fmt.Sprintf("%v:%v\n", k2, v2)
+							}
+						}
+						for k2, v2 := range mv.SpaceKV.Kv {
+							if bp.TableKV.Kv[k2] == "" {
+								bp.TableKV.Kv[k2] = v2
+								bp.TableKV.KvTag[k2] = mv.SpaceKV.KvTag[k2]
+								bp.Text += fmt.Sprintf("%v:%v\n", k2, v2)
+							}
+						}
+					}
+				}
+				tn.BPackage = true
+				tn.SortKV.RemoveKey(k)
+			}
+		}
+	}
+	//查找分包中的中标人排序
+	if tn.BlockPackage != nil && tn.BlockPackage.Map != nil && len(tn.BlockPackage.Map) > 0 {
+		for _, v := range tn.BlockPackage.Map {
+			vv := v.(*u.BlockPackage)
+			if vv.WinnerOrder == nil || len(vv.WinnerOrder) == 0 {
+				vv.WinnerOrder = winnerOrderEntity.Find(vv.Text, true, 2)
+			}
+		}
+	}
+	return
+}
+
+//组装解析到的分包
+func (tn *Table) assemblePackage(k1, v1, key string) {
+	bp := tn.BlockPackage.Map[key].(*u.BlockPackage)
+	if bp.TableKV == nil {
+		bp.TableKV = u.NewJobKv()
+	}
+	if v1 != "" {
+		k2, w1, v2, _, bf := CommonDataAnaly(k1, "中标情况", "", v1)
+		if bf {
+			for pos, k3 := range k2 {
+				if bp.TableKV.Kv[k3] == "" || w1[pos] > bp.TableKV.KvTag[k3].Weight {
+					bp.TableKV.Kv[k3] = v2
+					bp.TableKV.KvTag[k3] = &u.Tag{Value: v2, Weight: w1[pos]}
+				}
+			}
+		} else {
+			bp.TableKV.Kv[k1] = qutil.ObjToString(v1)
+		}
+	}
+	k1 = regReplAllSpace.ReplaceAllString(k1, "")
+	//拼接内容
+	if !excludeKey.MatchString(k1) {
+		bp.Text += fmt.Sprintf("%v:%v\n", k1, v1)
+	}
+	tn.BlockPackage.Map[key] = bp
+}
+
+/**
+之前爬虫过来的数据对table表格的抓取异常问题
+查找并修正不规则表格的字符串,只对全文做处理,块内的表格不需要修正
+**/
+var thbf = regexp.MustCompile("(?i)</?t(head|body|foot)>")
+
+//需要保留thead
+var saveThead = regexp.MustCompile("(?is)<thead>(.+?)</thead>")
+
+func RepairCon(con string) string {
+	res := saveThead.FindAllStringSubmatch(con, 1)
+	th := ""
+	if len(res) == 1 && len(res[0]) == 2 {
+		th = u.TrimLeftSpace(res[0][1], "")
+	}
+	con = thbf.ReplaceAllString(con, "")
+	con = u.TrimLeftSpace(con, "")
+	itbody := strings.Index(con, "<tr")
+	iLen := 3
+	if itbody == 0 {
+		con = findpos(con, iLen, itbody)
+	} else {
+		itable := strings.Index(con, "<table")
+		if itable == -1 || itable > itbody {
+			con = findpos(con, iLen, itbody)
+		}
+	}
+	//保留第一个thead
+	if th != "" {
+		con = strings.Replace(con, th, "<thead>"+th+"</thead>", 1)
+	}
+	//u.Debug(con)
+	return con
+}
+
+//修复表格
+func findpos(con string, iLen, start int) (newcon string) {
+	defer qutil.Catch()
+	n := len(con)
+	layer := 0
+	pos := 0
+	if start >= 0 {
+		if iLen == 6 {
+			for i := iLen + start; i < len(con); i++ {
+				if con[i] == '<' && i+6 < n {
+					str := con[i : i+6]
+					if str == "</tbod" {
+						if layer == 0 {
+							pos = i
+							break
+						} else {
+							layer--
+						}
+						i += 6
+					} else if str == "<tbody" {
+						layer++
+						i += 6
+					}
+				}
+			}
+			if pos+7 <= n && start+6 < pos {
+				newcon = con[:start] + "<table" + con[start+6:pos] + "</table" + con[pos+7:]
+			}
+		} else {
+			layer++
+			nq := 0
+			lasttr := 0
+			for i := iLen + start; i < len(con); i++ {
+				if con[i] == '<' && i+4 < n {
+					if nq == 0 {
+						str := con[i : i+4]
+						if str == "</tr" {
+							if layer <= 0 {
+								pos = i //正常情况不会存在此类情况
+								break
+							} else {
+								layer--
+								lasttr = i
+							}
+							i += 4
+						} else if str[:3] == "<tr" {
+							layer++
+							i += 4
+						} else if str == "<tab" && i+6 < n && con[i+4:i+6] == "le" {
+							if layer == 0 {
+								break
+							} else {
+								//内嵌的表格
+								nq++
+							}
+						}
+					} else {
+						if i+6 < n {
+							str := con[i : i+6]
+							if str == "</tabl" {
+								nq--
+							} else if str == "<table" {
+								nq++
+							}
+						} else {
+							break
+						}
+					}
+				}
+			}
+			if pos == 0 && lasttr > 3 {
+				pos = lasttr + 5
+			} else if pos > 0 {
+				pos += 5
+			}
+			if pos <= n {
+				newcon = con[:start] + "<table>" + con[start:pos] + "</table>" + con[pos:]
+			}
+		}
+	}
+	if newcon == "" {
+		newcon = con
+	}
+	return
+}
+
+//td的值里面有一个包,并且没有冒号kv
+func isHasOnePkgAndNoKv(v1 string) (bool, string) {
+	v1s := FindVal_1.FindAllString(v1, -1)
+	colonCount := len(regDivision.FindAllString(v1, -1))
+	if len(v1s) == 1 && colonCount < 2 {
+		ispkgcolon := regexp.MustCompile(v1s[0] + "[::]").MatchString(v1)
+		if (ispkgcolon && colonCount == 1) || (!ispkgcolon && colonCount == 0) {
+			return true, v1s[0]
+		}
+	}
+	return false, v1
+}
+
+//替换分包中混淆的词
+func replPkgConfusion(v1 string) string {
+	v1 = PreReg.ReplaceAllString(v1, "")
+	v1 = PreReg1.ReplaceAllString(v1, "")
+	v1 = PreCon.ReplaceAllString(v1, "")
+	v1 = PreCon2.ReplaceAllString(v1, "")
+	return v1
+}

+ 112 - 0
src/jy/pretreated/bys_test.go

@@ -0,0 +1,112 @@
+package pretreated
+
+import (
+	"fmt"
+	"log"
+	"regexp"
+	"strings"
+	"testing"
+	. "util"
+)
+
+var txt = `广西大通建设监理咨询管理有限公司崇左市城区棚户区改造项目(一期)推广策划及销售代理服务采购(采购项目编GXDTZB2016-13-CZ)中标结果公告
+访问次数:发布时间:2016-12-26
+ 广西大通建设监理咨询管理有限公司受崇左市壶城棚户区建设投资有限公司委托,根据《中华人民共和国政府采购法》等有关规定,于2016年11月28日就崇左市城区棚户区改造项目(一期)推广策划及销售代理服务采购项目采用公开招标方式进行采购,现就本次招标的中标结果公告如下:
+一、 采购项目名称及编号:崇左市城区棚户区改造项目(一期)推广策划及销售代理服务采购
+项目编号:GXDTZB2016-13-CZ
+二、采购项目简要说明:推广策划及销售代理服务。
+三、公告媒体:中国政府采购网www.ccgp.gov.cn、广西壮族自治区政府采购网www.gxzfcg.gov.cn、崇左市公共资源交易中心网http://www.czjyzx.gov.cn。
+四、评标日期:2016年12月22日
+评标地点:崇左市公共资源交易中心
+评标委员会名单:蒋洪涛、许荣珍、梁秀英、甘林川、与业主评委潘浩
+五、中标信息:
+1.中标供应商名称:广西合智房地产代理有限公司
+2.中标供应商地址:南宁市青秀区竹溪大道86号广源国际社区2栋1701室
+3.中标金额:住宅对外销售部分按对外销售面积*(43元/㎡以内)、对拆迁安置房部分按对拆迁安置房销售面积*(19元/㎡以内)计代理服务费
+六、联系事项:
+1.采购人名称:崇左市壶城棚户区建设投资有限公司
+地址:崇左市城南八路环保局科研综合楼内城投公司
+联系人及电话:沈工 0771-7836079
+2.采购代理机构:广西大通建设监理咨询管理有限公司
+地址:崇左市花山路嘉苑小区R-07号崇左公司
+联系人:黄工    电话/传真:0771-7883434
+3.监督部门:崇左市财政局政府采购监督管理部门
+电话0771-5962613
+七、中标结果公告期限:自中标结果公告发布之日起一个工作日。
+供应商认为中标结果使自己的权益受到损害的,可以在中标结果公告期限届满之日起七个工作日内以书面形式向采购人崇左市壶城棚户区建设投资有限公司或受托代理机构广西大通建设监理咨询管理有限公司提出质疑,逾期将不再受理。
+ 
+附:公开招标文件
+ 
+采购单位:崇左市壶城棚户区建设投资有限公司
+采购代理机构:广西大通建设监理咨询管理有限公司
+                                                日 期:2016年 12月23日
+附件
+公开招标文件.doc
+相关新闻`
+
+func TestBys(t *testing.T) {
+	//Init("../res/mlc/", "192.168.3.14:3379")
+	//找某一段落去识别,特点文字很少,5-15个字左右,一般是块标题
+	//	tmp := GetTags("这个项目投资总额")
+
+	//先分段
+	_segs := strings.FieldsFunc(txt, func(r rune) bool {
+		return r == 10 || r == 13
+	})
+	//再去除空行
+	segs := make([]*Segment, 0)
+	_index := 0
+	for _, seg := range _segs {
+		if seg != " " && len(seg) > 1 {
+			_seg := Segment{}
+			_index = _index + 1
+			_seg.Index = _index
+			_seg.Text = seg
+			segs = append(segs, &_seg)
+		}
+
+	}
+	//	blockArrays := make([]Block, 0)
+
+	blockMaps := make(map[string][]Segment)
+	currentTagsKey := ""
+	for _, seg := range segs {
+		r, _ := regexp.Compile("^[一|二|三|四|五|六|七|八|九|十][.|、| ](.{1,20})")
+		matchedStrArray := r.FindStringSubmatch((*seg).Text)
+		if len(matchedStrArray) > 1 {
+			currentTagsKey = matchedStrArray[1]
+			block_segs := make([]Segment, 0)
+			block_segs = append(block_segs, *seg)
+			blockMaps[matchedStrArray[1]] = block_segs
+		} else {
+			if currentTagsKey != "" {
+				blockMaps[currentTagsKey] = append(blockMaps[currentTagsKey], *seg)
+			}
+
+		}
+	}
+	fmt.Println("文章分块数:", len(blockMaps))
+	blockIndex := 1
+	for k, _ := range blockMaps {
+		if k != "" {
+			fmt.Println(blockIndex, "入参:", k, "..............标签:", GetTags(k))
+			blockIndex++
+		}
+
+	}
+
+}
+
+func TestReg(t *testing.T) {
+	str := `一.项目信息`
+	r, _ := regexp.Compile("^[一|二|三|四|五|六|七|八|九|十][.|、| ](.{1,20})")
+	matchedStrArray := r.FindStringSubmatch(str)
+	log.Println(matchedStrArray)
+}
+
+//
+func TestGetTags(t *testing.T) {
+	//	LoadConfig("../res/")
+	//tags := GetTags("招标要求")
+	//log.Println("tags ", tags)
+}

+ 782 - 0
src/jy/pretreated/colonkv.go

@@ -0,0 +1,782 @@
+//识别冒号kv
+package pretreated
+
+import (
+	"clear"
+	. "jy/util"
+	qutil "qfw/util"
+	"regexp"
+	"sort"
+	"strings"
+)
+
+type ColonkvEntity struct{}
+
+var (
+	colonkvEntity = &ColonkvEntity{}
+	regReplKV     = regexp.MustCompile("(.+?[\u4e00-\u9fa5))][\\s\u3000\u2003\u00a0]*[::].*[((]?[^\r\n\\s\u3000\u2003\u00a0]+?[))]?)[\\s\u3000\u2003\u00a0,。;;][((]?(.+?[\u4e00-\u9fa5))][\\s\u3000\u2003\u00a0]*[::].+)[))]?")
+	regReplKV2    = regexp.MustCompile("(.+?[\u4e00-\u9fa5))][\\s\u3000\u2003\u00a0]*[::].*[((]?[^\r\n\\s\u3000\u2003\u00a0标段包]+?[))]?)([一二三四五六七八九十]+[、..][^一二三四五六七八九十]+?)")
+	regKV         = regexp.MustCompile("([\\p{Han}][^,,。、.;;\r\n]{1,30}?)[::](.*)")
+	filterK       = regexp.MustCompile("[((\\[【].*?[))\\]】]|<[^>].+?>|[①②③¥·;;‘“'’”,*<>((\\[【、))/\\]】??,。.\".\\s\u3000\u2003\u00a0]+|^[一二三四五六七八九十0-91234567890]+")
+	filterValue   = regexp.MustCompile("^(无)$")
+	regReplKey    = regexp.MustCompile("^(包(.+[A-Za-z\\d])?|本项目|推荐|的|本次)|([约为元万亿]+|[大小]写|人民币|[全名]称|姓名)$")
+	BlockTagMap   = map[string]bool{
+		"招标范围": true,
+		"资格要求": true,
+	}
+	brackets = map[string]string{
+		"(": ")",
+		"(": ")",
+		"":  "",
+		"[": "]",
+		"【": "】",
+		"{": "}",
+		"{": "}",
+		"《": "》",
+		"<": ">",
+	}
+	//
+	PersonReg = regexp.MustCompile("[\u4e00-\u9fa5]{2,5}")
+	//
+	TelMustReg            = regexp.MustCompile("^" + PhoneReg.String() + "$")
+	PersonMustReg         = regexp.MustCompile("^" + PersonReg.String() + "$")
+	AddressReg            = regexp.MustCompile("[省市县区路号楼]")
+	BracketsTextReg       = regexp.MustCompile("[((]([^((]+)[))]")
+	ContactBuyerTitleReg  = regexp.MustCompile("采购联系事项")
+	ContactAgencyTitleReg = regexp.MustCompile("招标联系事项")
+)
+
+//一行多个冒号kv处理
+func (ce *ColonkvEntity) divisionMoreKV(con string) string {
+	con = regReplKV.ReplaceAllStringFunc(con, func(temp string) string {
+		//分kv的时候出现括号不成对出现的情况,分错了跳过
+		matchText := regReplKV.FindStringSubmatch(con)[1]
+		for k, v := range brackets {
+			if strings.Count(matchText, k) != strings.Count(matchText, v) {
+				return temp
+			}
+		}
+		return regReplKV.ReplaceAllString(temp, "$1\n\n$2")
+	})
+	con = regReplKV2.ReplaceAllString(con, "$1\n\n$2")
+	return con
+}
+
+//获取冒号kv入口
+func (ce *ColonkvEntity) entrance(con, title string, from int) ([]*Kv, map[string]string) {
+	kvs := ce.GetKvs(con, title, from)
+	kv := map[string]string{}
+	for _, v := range kvs {
+		if strings.TrimSpace(v.Value) == "" {
+			continue
+		}
+		kv[v.Key] = v.Value
+	}
+	return kvs, kv
+}
+
+//获取有序的kv
+func (ce *ColonkvEntity) GetKvs(con, title string, from int) []*Kv {
+	con = ce.processText(con)
+	kvs := ce.getColonKv(con, title, from)
+	return kvs
+}
+
+//处理正文
+func (ce *ColonkvEntity) processText(con string) string {
+	con = ce.divisionMoreKV(con)
+	for {
+		tmp := con
+		con = ce.divisionMoreKV(con)
+		if tmp == con {
+			break
+		}
+	}
+	return con
+}
+
+//分冒号kv
+//from 1--全文 2,3--table td
+func (ce *ColonkvEntity) getColonKv(con, title string, from int) []*Kv {
+	if from == 2 || from == 3 {
+		con = RemoveWarpOfTdVal(con)
+	}
+	findkvs := []*Kv{}
+	lines := spacekvEntity.getLines(con)
+	for index, line := range lines {
+		res := regKV.FindAllStringSubmatch(line, -1)
+		if len(res) > 0 {
+			for _, v := range res {
+				key, val := "", ""
+				if len(v) == 3 {
+					key = v[1]
+					val = v[2]
+				} else if len(v) == 4 {
+					key = v[2]
+					val = v[3]
+				}
+				//Debug("KV-key", key, val)
+				//Debug("KV-key", key, val)
+				//地址、联系人可能会重复 单位、代理机构的\时间、地点
+				if strings.TrimSpace(key) != "" {
+					prevLine, nextLine := "", ""
+					if index > 0 {
+						prevLine = lines[index-1]
+					}
+					if index < len(lines)-1 {
+						nextLine = lines[index+1]
+					}
+					findkvs = append(findkvs, &Kv{
+						Key:      key,
+						Value:    val,
+						Line:     line,
+						PrevLine: prevLine,
+						NextLine: nextLine,
+						Title:    title,
+					})
+					splitkeys := strings.Split(key, "/")
+					splitvalues := strings.Split(val, "/")
+					if len(splitkeys) > 1 && len(splitkeys) == len(splitvalues) {
+						for splitindex, splitkey := range splitkeys {
+							findkvs = append(findkvs, &Kv{
+								Key:      splitkey,
+								Value:    splitvalues[splitindex],
+								Line:     line,
+								PrevLine: prevLine,
+								NextLine: nextLine,
+								Title:    title,
+							})
+						}
+					}
+				}
+			}
+		}
+	}
+	return findkvs
+}
+
+//冒号kv和空格kv结合
+func (ce *ColonkvEntity) getColonSpaceKV(con string) []*Kv {
+	con = colonkvEntity.processText(con)
+	lines := spacekvEntity.getLines(con)
+	kvMaps := []*Kv{}
+	for _, line := range lines {
+		kvs := colonkvEntity.getColonKv(line, "", 1)
+		if len(kvs) == 0 {
+			kv := spacekvEntity.divideKV(line)
+			if kv != nil {
+				kvMaps = append(kvMaps, kv...)
+			}
+		} else {
+			kvMaps = append(kvMaps, kvs...)
+		}
+	}
+	return kvMaps
+}
+
+/*
+五、递交响应文件时间及地点
+1、时间:2016年5月20日14时00分至2016年5月20日14时30分(北京时间)
+2、地点:烟台开发区公共资源交易中心A座5楼会议室(金沙江路83号)
+key 时间 处理成 递交响应文件时间
+*/
+func (ce *ColonkvEntity) blockTitleKV(title, key string) string {
+	needKey := "时间"
+	if key != needKey {
+		return key
+	}
+	titles := regSplit.Split(title, -1)
+	for _, v := range titles {
+		if strings.HasSuffix(v, needKey) {
+			return v
+		}
+	}
+	return key
+}
+
+//根据配置文件中的规则,格式化正文
+func formatText(content, key string) string {
+	for _, v := range FormatTextMap[key] {
+		reg, _ := v["reg"].(*regexp.Regexp)
+		separator, isString := v["separator"].(string)
+		separators, isArray := v["separator"].([]interface{})
+		if isArray {
+			content = reg.ReplaceAllStringFunc(content, func(temp string) string {
+				for _, sv := range separators {
+					separator, _ := sv.(string)
+					if array := strings.Split(separator, "__"); separator != "__" && len(array) == 2 {
+						temp = regexp.MustCompile(array[0]).ReplaceAllString(temp, array[1])
+					}
+				}
+				return temp
+			})
+		} else if isString {
+			if array := strings.Split(separator, "__"); separator != "__" && len(array) == 2 {
+				content = reg.ReplaceAllStringFunc(content, func(temp string) string {
+					temp = regexp.MustCompile(array[0]).ReplaceAllString(temp, array[1])
+					return temp
+				})
+			} else {
+				content = reg.ReplaceAllString(content, separator)
+			}
+		}
+		//Debug(v["reg"], content)
+	}
+	return content
+}
+func IsContactKvHandle(value string, m map[string]bool) bool {
+	for k, _ := range m {
+		if k != value && (strings.HasPrefix(k, value) || strings.HasPrefix(value, k)) {
+			continue
+		}
+		if strings.Contains(value, k) || strings.Contains(k, value) {
+			return true
+		}
+	}
+	return false
+}
+
+//kv关于联系人信息的处理
+//采购人>集中采购机构
+/*
+func FormatContactKv(kvs *[]*Kv, title string, buyers []string) {
+	////////////////////////////
+	//处理联系人信息
+	var indexMap map[int]string
+	var matchMap map[string]map[string]bool
+	if contactFormat == nil || title != "" {
+		indexMap = map[int]string{}
+		matchMap = map[string]map[string]bool{}
+	} else {
+		indexMap = contactFormat.IndexMap
+		matchMap = contactFormat.MatchMap
+	}
+	////////////////////////////
+	totalIndexMap := map[string]bool{}
+	ascFind := true
+	ascFindFlag := len(indexMap) == 0 && buyers == nil
+	//采购人在联系人、电话后面的处理
+	isCanAddToIndexMap := false
+	for _, kv := range *kvs {
+		k := FilterContactKey(kv.Key)
+		k_length := len([]rune(k))
+		if k_length < 2 || k_length > 15 {
+			continue
+		}
+		isContinue := ContactInfoMustReg.MatchString(k)
+		if (isContinue || (ContactInfoVagueReg.MatchString(k) && IsMapHasValue(k, ContactType))) && ascFindFlag {
+			if len(indexMap) > 0 {
+				ascFind = true
+				ascFindFlag = false
+				indexMap = map[int]string{}
+			}
+			isCanAddToIndexMap = true
+		}
+		for _, ct_k := range HasOrderContactType(k) {
+			if !ContactType[ct_k].MatchString(k) {
+				continue
+			}
+			totalIndexMap[ct_k] = true
+			/////////////////////////////
+			if isContinue || !ascFindFlag {
+				continue
+			}
+			if isCanAddToIndexMap && len(indexMap) == 0 {
+				indexMap[1] = ct_k
+				ascFind = false
+			}
+		}
+	}
+	mustMatchFirst := len(indexMap) > 0 //第一个必须匹配上
+	titleMatch := false
+	if titleMatchType := ContactTypeTitleMatch(title); titleMatchType != "" {
+		titleMatch = true
+		mustMatchFirst = false
+		indexMap = map[int]string{1: titleMatchType}
+	}
+	//	if buyers == nil {
+	//		Debug("title-------", mustMatchFirst, title, indexMap, matchMap, totalIndexMap, ascFind)
+	//	}
+	//Debug("buyers-------", buyers)
+	//	if buyers == nil {
+	//		for _, kv := range *kvs {
+	//			Debug("bbbbbbbbbb", kv.Key, kv.Value)
+	//		}
+	//	}
+	startIndex := 0
+	prevKey := ""
+	index, notmatchCount, allMatchCount := 0, 0, 0
+	weightMap := map[string]map[string]interface{}{}     //权重
+	mapIndexInKvs := map[string]map[string]interface{}{} //map在数组总的索引位置
+	kvsTemp := make([]*Kv, len(*kvs))
+	copy(kvsTemp, *kvs)
+	for kv_index, kv := range *kvs {
+		isBreak := true
+		v := strings.TrimSpace(kv.Value)
+		//根据采购单位分析
+		isContinue := false
+		k := FilterContactKey(kv.Key)
+		k_length := len([]rune(k))
+		if buyers != nil {
+			for _, buyer := range buyers {
+				if buyer == "" {
+					continue
+				}
+				prevLine := kv.PrevLine
+				prevLine = strings.TrimSpace(prevLine)
+				prevLine = strings.Split(prevLine, " ")[0]
+				buyerLenght, prevLineLength := len([]rune(buyer)), len([]rune(prevLine))
+				prevNotEqual := true
+				if kv_index > 0 {
+					prevNotEqual = strings.TrimSpace(((*kvs)[kv_index-1]).Value) != buyer
+				}
+				matchBuyerSuccess := false
+				if strings.HasPrefix(k, buyer) && ContactInfoVagueReg.MatchString(k) && k_length-buyerLenght >= 2 && k_length-buyerLenght <= 5 {
+					matchBuyerSuccess = true
+					k = strings.TrimLeft(k, buyer)
+					k_length = len([]rune(k))
+					//					kvTemp := *kv
+					//					kvTemp.Key = strings.TrimLeft(k, buyer)
+					//					(*kvs)[kv_index] = &kvTemp
+				} else if k == buyer {
+					matchBuyerSuccess = true
+					if PersonMustReg.MatchString(v) {
+						k = "联系人"
+					} else if TelMustReg.MatchString(v) {
+						k = "联系电话"
+					} else if AddressReg.MatchString(v) {
+						k = "地址"
+					} else if PersonReg.MatchString(v) || PhoneReg.MatchString(v) {
+						k = "联系方式"
+					}
+					k_length = len([]rune(k))
+				} else if strings.HasPrefix(strings.TrimSpace(v), buyer) || (prevNotEqual && buyerLenght >= prevLineLength-5 && buyerLenght <= prevLineLength && strings.Contains(prevLine, buyer)) {
+					matchBuyerSuccess = true
+					isContinue = true
+				}
+				if matchBuyerSuccess {
+					isBreak = false
+					matchMap["采购单位"] = map[string]bool{}
+					indexMap[1] = "采购单位"
+					break
+				}
+			}
+		} else if ascFind {
+			for _, ct_k := range HasOrderContactType(k) {
+				if k_length < 3 || k_length > 15 {
+					isBreak = false
+					continue
+				}
+				if !ContactType[ct_k].MatchString(k) {
+					continue
+				}
+				if weightMap[ct_k] == nil {
+					weightMap[ct_k] = map[string]interface{}{}
+				}
+				isAddToMatchMap := false
+				addToMatchMapKey := ""
+				if ContactInfoVagueReg.MatchString(k) {
+					isAddToMatchMap = true
+					if matchMap[ct_k] == nil {
+						matchMap[ct_k] = map[string]bool{}
+					}
+					if !strings.HasSuffix(k, "方式") {
+						_, kTag := KvTagsToKV([]*Kv{&Kv{Key: k, Value: v}}, "", BuyerContacts, 1)
+						if len(kTag) == 1 {
+							tagVal, weightVal := FirstKeyValueInMap(kTag)
+							if tagVal == "采购单位联系人" && ContactBuyerPersonFilterReg.MatchString(v) {
+								isAddToMatchMap = false
+							}
+							if mapIndexInKvs[ct_k] == nil {
+								mapIndexInKvs[ct_k] = map[string]interface{}{}
+							}
+							myIndexInKvs := mapIndexInKvs[ct_k][tagVal]
+							if myIndexInKvs != nil {
+								if weightMap[ct_k][tagVal] == nil || (weightVal != nil && weightVal.(int) >= weightMap[ct_k][tagVal].(int)) {
+									weightMap[ct_k][tagVal] = weightVal.(int)
+									(*kvs)[myIndexInKvs.(int)] = kvsTemp[myIndexInKvs.(int)]
+									//(*kvs)[kv_index] = &Kv{Key: tagVal, Value: v}
+									kvTemp := *kv
+									kvTemp.Key = tagVal
+									kvTemp.Value = v
+									(*kvs)[kv_index] = &kvTemp
+								}
+							} else {
+								weightMap[ct_k][tagVal] = weightVal.(int)
+							}
+							mapIndexInKvs[ct_k][tagVal] = kv_index
+						}
+					}
+					addToMatchMapKey = k
+					if ct_k == "采购单位" {
+						k = ContactType[ct_k].FindString(k)
+					}
+				}
+				if ct_k == "采购单位" { //打标签,权重高的重新覆盖
+					_, kTag := KvTagsToKV([]*Kv{&Kv{Key: k, Value: v}}, "", []string{"采购单位"}, 1)
+					tagVal, weightVal := FirstKeyValueInMap(kTag)
+					if tagVal == ct_k {
+						if weightMap[ct_k][ct_k] == nil || (weightVal != nil && weightVal.(int) > weightMap[ct_k][ct_k].(int)) {
+							weightMap[ct_k][ct_k] = weightVal.(int)
+							matchMap[ct_k] = map[string]bool{}
+							isBreak = false
+							prevKey = ""
+						}
+					}
+				}
+				if isAddToMatchMap && !filterValue.MatchString(v) {
+					matchMap[ct_k][ContactInfoVagueReg.FindString(addToMatchMapKey)] = true
+				}
+				allMatchCount++
+				if IsMapHasValue(ct_k, indexMap) {
+					isContinue = true
+					continue
+				}
+				isBreak = false
+				if index != 0 || notmatchCount != 0 {
+					startIndex = 0
+					indexMap = map[int]string{}
+				}
+				if startIndex == 0 {
+					indexMap = map[int]string{}
+				}
+				prevKey = ""
+				startIndex++
+				indexMap[startIndex] = ct_k
+				isContinue = true
+			}
+		}
+		if isContinue {
+			continue
+		}
+		//		if buyers == nil {
+		//			Debug(ascFind, indexMap, k, v, matchMap, notmatchCount)
+		//		}
+		if len(indexMap) == 0 {
+			continue
+		}
+		if titleMatch && !ContactInfoMustReg.MatchString(k) {
+			k = ContactInfoVagueReg.FindString(k)
+			k_length = len([]rune(k))
+		}
+		if k_length < 2 || k_length > 10 {
+			isBreak = false
+			continue
+		}
+		if !ContactInfoMustReg.MatchString(k) {
+			if mustMatchFirst {
+				mustMatchFirst = false
+				continue
+			}
+			if buyers == nil && len(totalIndexMap) != 0 {
+				isBreak = false
+			}
+			//允许有这么多个匹配不上的key
+			notmatchCount++
+			if notmatchCount < len(indexMap)*2 {
+				isBreak = false
+			} else if contactFormat == nil && ascFind {
+				startIndex = 0
+				notmatchCount = 0
+				indexMap = map[int]string{}
+				//matchMap = map[string]map[string]bool{}
+			}
+			continue
+		}
+		isBreak = false
+		if prevKey != k {
+			prevKey = k
+			index = 1
+		} else if prevKey == k {
+			index++
+		}
+		//过滤值
+		if filterValue.MatchString(v) {
+			continue
+		}
+		myContactType := indexMap[index]
+		if myContactType == "" {
+			continue
+		}
+		//		if buyers == nil {
+		//			Debug(ascFind, indexMap, k, v, matchMap, notmatchCount)
+		//		}
+		if strings.HasSuffix(k, "方式") && TelMustReg.MatchString(v) {
+			k = "联系电话"
+		}
+		if matchMap[myContactType] == nil {
+			matchMap[myContactType] = map[string]bool{}
+		}
+		myTagValue := ContactInfoMustReg.FindString(k)
+		if myTagValue == "" && titleMatch {
+			myTagValue = ContactInfoVagueReg.FindString(k)
+		}
+		if IsContactKvHandle(myTagValue, matchMap[myContactType]) {
+			continue
+		}
+		matchMap[myContactType][myTagValue] = true
+		if ContactType[myContactType].MatchString(k) {
+			continue
+		}
+		allMatchCount++
+		delete(totalIndexMap, myContactType)
+		if !strings.HasSuffix(k, "方式") {
+			_, kTag := KvTagsToKV([]*Kv{&Kv{Key: myContactType + k, Value: v}}, "", BuyerContacts, 1)
+			if len(kTag) == 1 {
+				tagVal, _ := FirstKeyValueInMap(kTag)
+				if tagVal == "采购单位联系人" && ContactBuyerPersonFilterReg.MatchString(v) {
+					continue
+				}
+				if mapIndexInKvs[myContactType] == nil {
+					mapIndexInKvs[myContactType] = map[string]interface{}{}
+				}
+				myIndexInKvs := mapIndexInKvs[myContactType][tagVal]
+				if myIndexInKvs != nil {
+					(*kvs)[myIndexInKvs.(int)] = kvsTemp[myIndexInKvs.(int)]
+				}
+				mapIndexInKvs[myContactType][tagVal] = kv_index
+				if weightMap[myContactType] == nil {
+					weightMap[myContactType] = map[string]interface{}{}
+				}
+				weightMap[myContactType][tagVal] = 1
+			}
+		}
+		//(*kvs)[kv_index] = &Kv{Key: myContactType + k, Value: v}
+		kvTemp := *kv
+		kvTemp.Key = myContactType + k
+		kvTemp.Value = v
+		(*kvs)[kv_index] = &kvTemp
+		if ascFind && isBreak && len(indexMap) > 0 {
+			break
+		}
+	}
+	if allMatchCount == 0 && len(*kvs) > 0 {
+		indexMap = map[int]string{}
+		matchMap = map[string]map[string]bool{}
+	}
+	if contactFormat != nil {
+		(*contactFormat).IndexMap = indexMap
+		(*contactFormat).MatchMap = matchMap
+	}
+	//	if buyers == nil {
+	//		for _, kv := range *kvs {
+	//			Debug("bbbbbbbbbb", kv.Key, kv.Value)
+	//		}
+	//	}
+	//Debug("totalIndexMap", len(totalIndexMap))
+}
+*/
+func ContactTypeTitleMatch(title string) string {
+	matchType := ""
+	if title != "" && len([]rune(title)) < 15 {
+		if ContactBuyerTitleReg.MatchString(title) {
+			matchType = "采购单位"
+		} else if ContactAgencyTitleReg.MatchString(title) {
+			matchType = "代理机构"
+		} else {
+			for _, ct_k := range HasOrderContactType(title) {
+				if ContactType[ct_k].MatchString(title) {
+					matchType = ct_k
+					break
+				}
+			}
+		}
+	}
+	return matchType
+}
+
+//获取带有排序的联系人类型
+func HasOrderContactType(text string) []string {
+	indexs := []int{}
+	indexMap := map[int]string{}
+	temp := []string{}
+	for k, v := range ContactType {
+		s := v.FindStringIndex(text)
+		if len(s) > 1 {
+			if indexMap[s[0]] != "" {
+				temp = append(temp, k)
+			} else {
+				indexs = append(indexs, s[0])
+				indexMap[s[0]] = k
+			}
+		}
+	}
+	sort.Ints(indexs)
+	result := []string{}
+	for _, v := range indexs {
+		result = append(result, indexMap[v])
+	}
+	if len(temp) > 0 {
+		result = append(result, temp...)
+	}
+	return result
+}
+
+//两种冒号kv结合到一起
+//from 1--全文 2--table td 3--table td解析采购单位联系人 4--分包
+func GetKVAll(content, title string, from int) *JobKv {
+	content = formatText(content, "kv")
+	m1Kvs, _ := colonkvEntity.entrance(content, title, from)
+	m1, m1Weight := KvTagsToKV(m1Kvs, title, nil, from)
+	if m1 == nil {
+		m1 = map[string]string{}
+	}
+	m2Kvs, m2, m2Weight := GetKvFromtxt(content, title, from)
+	for k, v := range m2 {
+		if m1[k] == "" {
+			m1[k] = v
+			m1Weight[k] = m2Weight[k]
+		}
+	}
+	return &JobKv{
+		Kvs:   m1Kvs,
+		Kvs_2: m2Kvs,
+		Kv:    m1,
+		KvTag: m1Weight,
+	}
+}
+
+//KVTags转kv
+func KvTagsToKV(findkvs []*Kv, title string, tagdbs []string, from int) (map[string]string, map[string]*Tag) {
+	kvTags := map[string]*Tag{}
+	if title != "" && BlockTagMap[title] {
+		kvTags[title] = &Tag{title, 0}
+	}
+	for _, findkv := range findkvs {
+		kvMap := map[string]string{}
+		k, val := findkv.Key, findkv.Value
+		//val是空的话,不打标签
+		if filterValue.MatchString(val) {
+			continue
+		}
+		key := k
+		key = ClearKey(key, 1)
+		if key == "" {
+			continue
+		}
+		key = colonkvEntity.blockTitleKV(title, key)
+		//先用新的key
+		tags := GetAppointTags(key, tagdbs)
+		if len(tags) == 0 && len(key) < 10 && len(title) > 0 && len(title) < 15 {
+			key = title + key
+			tags = GetAppointTags(key, tagdbs)
+		}
+		//再用老的key
+		if len(tags) == 0 && k != key {
+			tags = GetAppointTags(k, tagdbs)
+			if len(tags) == 0 && len(k) < 10 && len(title) > 0 && len(title) < 15 {
+				k = title + k
+				tags = GetAppointTags(k, tagdbs)
+				if len(tags) > 0 {
+					key = k
+				}
+			}
+		}
+		if len(tags) == 0 {
+			//go AddtoNoMatchMap(key)
+			//Debug(key)
+			//continue
+			//由跳过修改为保留
+			tags = []*Tag{&Tag{k, -100}}
+		}
+		for _, tk := range tags {
+			//分包过来给kv打标签的时候,只取第一个,后面的不覆盖
+			if kvTags[tk.Value] == nil || (kvTags[tk.Value].Weight < tk.Weight && from != 4) {
+				fc := StandardNameMap[tk.Value]
+				if (fc != nil && fc.CheckNum) || (moneyreg.MatchString(tk.Value)) {
+					val += GetMoneyUnit(k, val)
+				}
+				//Debug("KV-key", tk, val)
+				kvTags[tk.Value] = &Tag{val, tk.Weight}
+				kvMap[tk.Value] = val
+				//Debug("KV-key", tk.Value, val, key, tk.Weight)
+			}
+		}
+	}
+	//
+	kv := map[string]string{}
+	kvWeight := map[string]*Tag{}
+	if len(kvTags) > 0 {
+		for k, v := range kvTags {
+			if kv[k] != "" {
+				continue
+			}
+			kv[k] = v.Value
+			kvWeight[k] = v
+		}
+	}
+	return kv, kvWeight
+}
+
+func FilterContactKey(key string) string {
+	key1 := ""
+	for _, v := range BracketsTextReg.FindAllString(key, -1) {
+		for _, vv := range ContactType {
+			if vv.MatchString(v) {
+				if len([]rune(v)) < 3 || len([]rune(v)) > 10 {
+					continue
+				}
+				key1 = v
+				break
+			}
+		}
+	}
+	key = filterK.ReplaceAllString(key, "")
+	key = tablekeyclear.ReplaceAllString(key, "")
+	return key1 + key
+}
+
+//td里的内容,调用这边的方法分kv的时候,有的带有换行,清理掉
+func RemoveWarpOfTdVal(text string) string {
+	//只有一个冒号
+	if len(regDivision.FindAllString(text, -1)) != 1 {
+		return text
+	}
+	text = strings.TrimSpace(text)
+	//有一个换行
+	array := strings.Split(text, "\n")
+	if len(array) != 2 {
+		return text
+	}
+	//第一行以冒号结尾
+	if !colonEndReg.MatchString(array[0]) {
+		if BracketsTextReg.ReplaceAllString(array[1], "") == "" {
+			text = array[0] + array[1]
+		}
+		return text
+	}
+	text = array[0] + array[1]
+	return text
+}
+
+//打标签的时候,清理key
+//from 1--冒号key 2--table key
+func ClearKey(k string, from int) string {
+	for {
+		old := k
+		if from == 1 {
+			k = filterK.ReplaceAllString(k, "")
+		}
+		k = tablekeyclear.ReplaceAllString(k, "")
+		k = regReplKey.ReplaceAllString(k, "")
+		if old == k {
+			break
+		}
+	}
+	return k
+}
+
+//获取金额的单位
+func GetMoneyUnit(key, val string) string {
+	if !(strings.Index(val, "元") > 0 || strings.Index(val, "万") > 0 || strings.Index(val, "亿") > 0) {
+		mv := clear.ObjToMoney([]interface{}{val, val})
+		if len(mv) > 0 && qutil.IntAll(mv[0]) > 0 {
+			for _, dw := range []string{"万", "亿"} {
+				if strings.Index(key, dw) > 0 {
+					return dw
+				}
+			}
+		}
+	}
+	return ""
+}

+ 767 - 0
src/jy/pretreated/division.go

@@ -0,0 +1,767 @@
+package pretreated
+
+import (
+	"jy/util"
+	qutil "qfw/util"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+//分块、分段功能
+var (
+	regSerialTitles = []string{
+		"([一二三四五六七八九十]+)[\u3000\u2003\u00a0\\s]*[、..::,](.*)",
+		"[((]([一二三四五六七八九十]+)[))][\u3000\u2003\u00a0\\s]*[、..::]?(.*)",
+		"(\\d+)[\u3000\u2003\u00a0\\s]*、(.*)",
+		"(\\d+)[\u3000\u2003\u00a0\\s]*[..]([^\\d][^\r\n]+)",
+		"(\\d+)[\u3000\u2003\u00a0\\s]+([^\\d][^\r\n]+)",
+		"1[..](\\d+)[\u3000\u2003\u00a0\\s]+([^\\d..][^\r\n]+)",
+	}
+	regSerialTitles_1 = []*regexp.Regexp{
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s]*|^[\u3000\u2003\u00a0\\s]*)([一二三四五六七八九十]+)[\u3000\u2003\u00a0\\s]*[、..::,](.*)"),
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s]*|^[\u3000\u2003\u00a0\\s]*)[((]([一二三四五六七八九十]+)[))][\u3000\u2003\u00a0\\s]*[、..::]?(.*)"),
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s]*|^[\u3000\u2003\u00a0\\s]*)(\\d+)[\u3000\u2003\u00a0\\s]*、(.*)"),
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s]*|^[\u3000\u2003\u00a0\\s]*)(\\d+)[\u3000\u2003\u00a0\\s]*[..]([^\\d][^\r\n]+)"),
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s]*|^[\u3000\u2003\u00a0\\s]*)(\\d+)[\u3000\u2003\u00a0\\s]+([^\\d][^\r\n]+)"),
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s]*|^[\u3000\u2003\u00a0\\s]*)1[..](\\d+)[\u3000\u2003\u00a0\\s]+([^\\d..][^\r\n]+)"),
+		regexp.MustCompile("([\r\n][\u3000\u2003\u00a0\\s(]*|^[\u3000\u2003\u00a0\\s(]*)(\\d+)[\u3000\u2003\u00a0\\s)]+([^\r\n]+)"),
+	}
+	regSerialTitles_2 = []*regexp.Regexp{
+		regexp.MustCompile("^([一二三四五六七八九十]+)[\u3000\u2003\u00a0\\s]*[、..::,](.*)$"),
+		regexp.MustCompile("^[((]([一二三四五六七八九十]+)[))][\u3000\u2003\u00a0\\s]*[、..::]?(.*)$"),
+		regexp.MustCompile("^(\\d+)[\u3000\u2003\u00a0\\s]*、(.*)$"),
+		regexp.MustCompile("^(\\d+)[\u3000\u2003\u00a0\\s]*[..]([^\\d][^\r\n]+)$"),
+		regexp.MustCompile("^(\\d+)[\u3000\u2003\u00a0\\s]+([^\\d][^\r\n]+)$"),
+		regexp.MustCompile("^1[..](\\d+)[\u3000\u2003\u00a0\\s]+([^\\d..][^\r\n]+)$"),
+		regexp.MustCompile("^[(](\\d+)[\u3000\u2003\u00a0\\s)]+([^\r\n]+)$"),
+	}
+	regReplAllTd       = regexp.MustCompile("(?smi)<td.*?>.+?</td>")
+	regIsNumber        = regexp.MustCompile("^\\d+$")
+	regIsChineseNumber = regexp.MustCompile("^[一二三四五六七八九十]+$")
+	regReplAllSpace    = regexp.MustCompile("[\u3000\u2003\u00a0\\s]+")
+	regTrimSpace       = regexp.MustCompile("^[\u3000\u2003\u00a0\\s]+|[\u3000\u2003\u00a0\\s]+$")
+	regReplWrapSpace   = regexp.MustCompile("^[\r\n][\u3000\u2003\u00a0\\s]*|[\r\n][\u3000\u2003\u00a0\\s]*$")
+	regReplAllSymbol   = regexp.MustCompile("[(\\(<《【\\[{{〔)\\)>》】\\]}}〕,,;;::'\"“”。.\\??/+=\\-_——*&……\\^%$¥@#!!`~·]")
+	regFilterTitle     = regexp.MustCompile("[(\\(<《【\\[{{〔].+?[)\\)>》】\\]}}〕]")
+	regDivision        = regexp.MustCompile("[::]")
+	regSpliteSegment   = regexp.MustCompile("[\r\n]")
+	regFilterNumber    = regexp.MustCompile("^[\\d一二三四五六七八九十]+")
+	regSplit           = regexp.MustCompile("和|以?及|与|、")
+	regStartWrap       = regexp.MustCompile("^[\r\n]")
+	regEndWrap         = regexp.MustCompile("[\r\n]$")
+	regMoreWrap        = regexp.MustCompile("[\r\n]{2,}")
+	replSerial         = regexp.MustCompile("(\r\n|^)([\\d一二三四五六七八九十][、..::,])+\\d")
+	moreColonReg       = regexp.MustCompile("[::]+")
+	//查找分包之前,先对内容进行预处理
+	/*
+		第一包:采购设备清单
+		<table></table>
+	*/
+	regPackageFilter  = regexp.MustCompile("([第]?([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)((子|合同|分|施工|监理)?(标段?|包|合同段|标包))|((子|合同|分|施工|监理)?(标|包)(段|号)?)[  \u3000\u2003\u00a0]*([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)).+[\r\n]?<table>")
+	filterPkgTitleKey = regexp.MustCompile("结果[::]?$")
+)
+
+//分块
+func DivideBlock(content string, from int) ([]*util.Block, int) {
+	defer qutil.Catch()
+	returnValue := 0
+	var blocks []*util.Block
+	if strings.TrimSpace(content) == "" {
+		return blocks, -1
+	}
+	//table里面的内容不考虑,先把table清理掉
+	//contentTemp := regReplAllTd.ReplaceAllString(content, "")
+	contentTemp := TextAfterRemoveTable(content)
+	tdIndexs := regReplAllTd.FindAllStringSubmatchIndex(content, -1)
+	regContenSerialTitle, regSerialTitleIndex := getSerialType(contentTemp)
+	//没有分块
+	if regSerialTitleIndex == -1 {
+		if len(contentTemp) == len(content) {
+			//没有分块
+			return blocks, -1
+		} else { //有table
+			return blocks, -2
+		}
+	}
+	//匹配序号和标题
+	regSerialTitle := regSerialTitles_2[regSerialTitleIndex]
+	indexs := regContenSerialTitle.FindAllStringIndex(content, -1)
+	indexs = filterSerial(content, indexs, tdIndexs)
+	//头块
+	var headBlock, endBlock *util.Block
+	currentIndex := 0
+	for k, v := range indexs {
+		start, end := v[0], v[1]
+		//添加开头部分
+		if k == 0 {
+			if headTemp := content[:start]; regReplAllSpace.ReplaceAllString(headTemp, "") != "" {
+				headBlock = &util.Block{
+					Index: -1,       //序号
+					Text:  headTemp, //内容
+					Title: "",       //标题
+					Start: 0,
+					End:   start,
+				}
+			}
+		}
+		//分块
+		blockSerialTitle := regTrimSpace.ReplaceAllString(content[start:end], "")
+		serialTitles := regSerialTitle.FindStringSubmatch(blockSerialTitle) //序号和标题
+		if len(serialTitles) < 3 {
+			continue
+		}
+		indexSting := regReplAllSpace.ReplaceAllString(serialTitles[1], "") //序号
+		index := 0
+		//转成数字序号
+		if regIsNumber.MatchString(indexSting) {
+			index, _ = strconv.Atoi(indexSting)
+		} else if regIsChineseNumber.MatchString(indexSting) {
+			index = util.ChineseNumberToInt(indexSting)
+		}
+		//序号开始就是错误的
+		if k+1 != index {
+			if k == 0 {
+				returnValue = 3
+			} else {
+				if currentIndex+1 != index {
+					//如果序号不是连续的,不往下走
+					returnValue = 2
+					//添加结尾部分
+					if from != 3 {
+						endBlock = &util.Block{
+							Index: -2,              //序号
+							Text:  content[start:], //内容
+							Title: "",              //标题
+							Start: start,
+							End:   len(content),
+						}
+						break
+					}
+				}
+			}
+			currentIndex = index
+		}
+
+		//
+		title := serialTitles[2]                         //标题
+		title = regTrimSpace.ReplaceAllString(title, "") //清除前后空格
+		//分块后的块文
+		nextStart := len(content)
+		if k < len(indexs)-1 {
+			nextStart = indexs[k+1][0]
+		}
+		//获取块中除了序号和标题的内容
+		blockText := regTrimSpace.ReplaceAllString(content[end:nextStart], "")
+		var titles = []string{}
+		if title != "" {
+			blockTextTemp := regReplAllSpace.ReplaceAllString(blockText, "")
+			//特殊情况处理
+			if blockTextTemp == "" {
+				if regDivision.MatchString(title) {
+					/*
+						一、项目编号:HMEC170223
+						二、项目名称:执法记录仪采购
+					*/
+					blockText = title
+					divisionIndexs := regDivision.FindStringIndex(title)
+					title = title[:divisionIndexs[0]]
+				} else {
+					/*
+						十一、投标代表须持本人身份证原件亲自递交投标文件,代理机构项目经理审核通过后,办理签收手续,否则投标文件被拒收。
+						十二、开标时间:2017年3月20日9时30分
+					*/
+					blockText = title
+				}
+			} else if blockTextTemp != "" && regDivision.MatchString(title) {
+				/*
+					2、采购单位名称:福建省汀州医院
+					采购单位地址: 龙岩市长汀县
+					联系人:胡科长
+					联系方式:0597-6826353
+				*/
+				//多个标题
+				divisionIndexs := regDivision.FindStringIndex(title)
+				titleBefore := regReplAllSpace.ReplaceAllString(title[:divisionIndexs[0]], "")
+				titleAfter := regReplAllSpace.ReplaceAllString(title[divisionIndexs[1]:], "")
+				if titleAfter != "" {
+					titles = append(titles, titleBefore)
+					//分段 去每一个冒号前面的key
+					segments := regSpliteSegment.Split(blockText, -1)
+					for _, sv := range segments {
+						divisionIndexs = regDivision.FindStringIndex(sv)
+						if len(divisionIndexs) == 0 {
+							continue
+						}
+						titleTemp := regReplAllSpace.ReplaceAllString(sv[:divisionIndexs[0]], "")
+						if titleTemp == "" {
+							continue
+						}
+						titles = append(titles, titleTemp)
+					}
+					blockText = title + "\n" + blockText
+					title = ""
+				} else {
+					blockText = title + "\n" + blockText
+					title = titleBefore
+				}
+			} else {
+				blockText = title + "\n" + blockText
+			}
+		}
+		if len(titles) == 0 {
+			titles = append(titles, title)
+		}
+		//没有内容的块,不打标签,不分段
+		if blockText == "" {
+			continue
+		}
+		//过滤
+		if regexp.MustCompile("投标文件格式|业绩").MatchString(title) {
+			continue
+		}
+		blockText = hasMergeKV(title, blockText)
+		block := &util.Block{
+			Index: index,     //序号
+			Text:  blockText, //内容
+			Title: title,     //标题
+			Start: start,
+			End:   nextStart,
+		}
+		//
+		titleIsExists := map[string]bool{} //去重
+		for _, tv := range titles {
+			tv = filterTitle(tv)
+			//分割标题 [和及]。。。
+			splitTitles := regSplit.Split(tv, -1)
+			for _, sv := range splitTitles {
+				if sv == "" || titleIsExists[sv] {
+					continue
+				}
+				titleIsExists[sv] = true
+				//标题过短过长不打标签
+				if len([]rune(sv)) >= 2 && len([]rune(sv)) <= 10 {
+					//打标签
+					block.Tags = append(block.Tags, util.GetBlockTags(sv))
+				}
+			}
+		}
+		tagsToBlocks(blocks, block)
+		//log.Println(index, sv, splitTitles)
+		//log.Println(blockText)
+		blocks = append(blocks, block)
+	}
+	var returnBlocks []*util.Block
+	if len(blocks) > 0 {
+		//头
+		if headBlock != nil {
+			returnBlocks = append(returnBlocks, headBlock)
+		}
+		//中间块
+		returnBlocks = append(returnBlocks, blocks...)
+		//尾
+		if endBlock != nil {
+			returnBlocks = append(returnBlocks, endBlock)
+		}
+		if returnValue == 0 {
+			returnValue = 1
+		}
+	}
+
+	for _, bl := range returnBlocks {
+		//解析kv
+		newText := TextAfterRemoveTable(bl.Text)
+		bl.ColonKV = GetKVAll(newText, bl.Title, from)
+		bl.SpaceKV = spacekvEntity.entrance(newText, bl.Title)
+		//正则抽取的时候有时需要匹配换行或者句号,这里在解析完kv之后,在块结尾添加换行和句号
+		bl.Text = appendWarpStop(bl.Text)
+	}
+	return returnBlocks, returnValue
+}
+
+//有合并kv的 例如项目名称及编号
+func hasMergeKV(title, text string) string {
+	title = regDivision.ReplaceAllString(title, "")
+	titles := regSplit.Split(title, -1)
+	if len(titles) <= 1 {
+		return text
+	}
+	before := titles[0]
+	after := titles[1]
+	if strings.Contains(title, "项目") && len([]rune(after)) == 2 {
+		after = "项目" + after
+	} else {
+		return text
+	}
+	if strings.Count(text, "\n") != 1 {
+		return text
+	}
+	texts := strings.Split(text, "\n")
+	textOneLine := texts[0]
+	textTwoLine := texts[1]
+	if regDivision.MatchString(textTwoLine) {
+		return text
+	}
+	if textTwoLine := strings.SplitN(textTwoLine, ",", 2); len(textTwoLine) == 2 {
+		text = textOneLine + "\n" + before + ":" + textTwoLine[0] + "," + after + ":" + textTwoLine[1]
+	}
+	return text
+}
+
+//过滤序号,判断序号是不是在td里,如果是的话这个序号作废
+func filterSerial(content string, indexs, tdIndexs [][]int) [][]int {
+	returnIndexs := [][]int{}
+	for _, v := range indexs {
+		flag := false
+		//根据序号的开始位置,判断是不是在td里面
+		for _, tv := range tdIndexs {
+			if v[0] > tv[0] && v[0] < tv[1] {
+				flag = true
+				continue
+			}
+		}
+		if flag {
+			continue
+		}
+		returnIndexs = append(returnIndexs, []int{v[0], v[1]})
+	}
+	return returnIndexs
+}
+
+//获取正文所用的序号类型
+func getSerialType(content string) (*regexp.Regexp, int) {
+	var regContenSerialTitle *regexp.Regexp
+	//先判断文章最外层使用的是哪种序号
+	contentStartIndex, regSerialTitleIndex := -1, -1
+	for k, v := range regSerialTitles_1 {
+		indexs := v.FindStringIndex(content)
+		//只用最外层的序号,里面的过滤掉
+		if len(indexs) == 2 && !regSpliteSegment.MatchString(strings.TrimSpace(content[indexs[0]:indexs[1]])) && (contentStartIndex == -1 || indexs[0] < contentStartIndex) {
+			regSerialTitleIndex = k
+			contentStartIndex = indexs[0]
+			regContenSerialTitle = v
+		}
+	}
+	return regContenSerialTitle, regSerialTitleIndex
+}
+
+//添加换行和句号
+func appendWarpStop(text string) string {
+	//清理前后空格
+	text = regTrimSpace.ReplaceAllString(text, "")
+	//添加句号
+	if !strings.HasSuffix(text, "。") {
+		text += "。"
+	}
+	//添加换行
+	if !regEndWrap.MatchString(text) {
+		text += "\n"
+	}
+	return text
+}
+
+//分段
+func DivideSegment(txt string) []*util.Segment {
+	//先分段
+	_segs := strings.FieldsFunc(txt, func(r rune) bool {
+		return r == 10 || r == 13
+	})
+	//再去除空行
+	segs := make([]*util.Segment, 0)
+	_index := 0
+	for _, seg := range _segs {
+		if seg != " " && len(seg) > 1 {
+			_seg := util.Segment{}
+			_index = _index + 1
+			_seg.Index = _index
+			_seg.Text = seg
+			segs = append(segs, &_seg)
+		}
+
+	}
+	return segs
+}
+
+/** 给块打标签 **/
+func tagsToBlocks(blocks []*util.Block, block *util.Block) {
+	if len(block.Tags) == 0 {
+		return
+	}
+	tag := map[string]bool{}
+	tagWeight := map[string]int{}
+	for _, v := range block.Tags {
+		for _, ts := range v {
+			tag[ts.Value] = true
+			tagWeight[ts.Value] = ts.Weight
+		}
+	}
+	for v, _ := range tag {
+		for _, block := range blocks {
+			if block.Tag[v] {
+				for _, blockTags := range block.Tags {
+					for _, ts := range blockTags {
+						if ts.Value == v && ts.Weight < tagWeight[v] {
+							block.Tag[v] = false
+						}
+					}
+				}
+			}
+		}
+	}
+	block.Tag = tag
+}
+
+func filterTitle(title string) string {
+	//清理空格
+	title = regReplAllSpace.ReplaceAllString(title, "")
+	//清理成对出现的符号中的内容
+	title = regFilterTitle.ReplaceAllString(title, "")
+	//清理特殊符号
+	title = regReplAllSymbol.ReplaceAllString(title, "")
+	//清理序号
+	title = regFilterNumber.ReplaceAllString(title, "")
+	return title
+}
+
+//从块里面找分包
+func FindPackageFromBlocks(blocks *[]*util.Block, title string) (blockPackage map[string]*util.BlockPackage) {
+	blockPackage = map[string]*util.BlockPackage{}
+	//块分包
+	for _, v := range *blocks {
+		text := regPackageFilter.ReplaceAllString(v.Text, "<table>")
+		text = TextAfterRemoveTable(text)
+		if text == "" {
+			continue
+		}
+		ok, surplusText := divisionPackageChild(&blockPackage, text, title, true, v.Tag["中标单位"])
+		//把分包内容摘除掉有问题 有的项目名称中包含二标段
+		if ok && false {
+			v.Text = surplusText
+			v.ColonKV = GetKVAll(surplusText, v.Title, 1)
+			v.SpaceKV = spacekvEntity.entrance(surplusText, v.Title)
+		}
+	}
+	return
+}
+
+//从正文里面找分包
+func FindPackageFromText(title string, content string) (blockPackage map[string]*util.BlockPackage) {
+	blockPackage = map[string]*util.BlockPackage{}
+	//从正文里面找分包
+	divisionPackageChild(&blockPackage, content, title, true, false)
+	return
+}
+
+//分块之后分包
+func divisionPackageChild(blockPackage *map[string]*util.BlockPackage, content, title string, isFindWinnerOrder, accuracy bool) (bool, string) {
+	//查找知否有分包
+	content = regMoreWrap.ReplaceAllString(content, "\n")
+	content = regEndWrap.ReplaceAllString(content, "")
+	con, pkg, flag := CheckMultiPackage(content, title)
+	if !flag {
+		return false, ""
+	}
+	//	util.Debug(con)
+	//	util.Debug(pkg)
+	//分包前面添加换行
+	appendWarpIndex := []int{}
+	for _, v := range pkg {
+		//如果文本内容以识别出来的分包标识结尾,不是分包
+		if len(pkg) == 1 && strings.HasSuffix(con, v[0]) {
+			return false, ""
+		}
+		//
+		is := regexp.MustCompile(v[0]+"[::]*").FindAllStringIndex(con, -1)
+		for _, sv := range is {
+			appendWarpIndex = append(appendWarpIndex, sv[0])
+		}
+	}
+	appendWarpIndex = getPkgIndex(appendWarpIndex)
+	conTemp := ""
+	for k, v := range appendWarpIndex {
+		if k == 0 {
+			conTemp += con[:v] + "\n"
+		} else {
+			conTemp += "\n" + con[appendWarpIndex[k-1]:v]
+		}
+		if k == len(appendWarpIndex)-1 {
+			conTemp += "\n" + con[v:]
+		}
+	}
+	con = conTemp
+	con = replSerial.ReplaceAllString(con, "\n")
+	con = regMoreWrap.ReplaceAllString(con, "\n")
+	//util.Debug(con)
+	//根据分包,找索引位置
+	indexMap := map[int]int{}
+	indexKeyStringMap := map[int]string{}
+	indexKeyIntMap := map[int]int{}
+	indexs := []int{}
+	startEndMap := map[int]int{}
+	pkgIndexMap := map[string][]int{}
+	indexPkgMap := map[int]string{}
+	//遍历分包,把kv在包前面的移动到包后面
+	for _, v := range pkg {
+		pgflag := v[0] + "[::]*"
+		is := regexp.MustCompile(pgflag).FindAllStringIndex(con, -1)
+		for _, sv := range is {
+			indexMap[sv[0]] = sv[1]
+			indexs = append(indexs, sv[0])
+			pkgIndexMap[v[0]] = append(pkgIndexMap[v[0]], sv[0])
+			indexPkgMap[sv[0]] = v[0]
+		}
+		//key在包前面,并且在一行的开头
+		keys := regexp.MustCompile("([\r\n]|^)([\u4e00-\u9fa5]{2,30}?([((].{1,8}?[))])?[::\\s\u3000\u2003\u00a0]+.*?)"+pgflag).FindAllStringSubmatchIndex(con, -1)
+		if len(keys) == 0 {
+			//key在包前面,并且key以冒号结尾
+			keys = regexp.MustCompile("()([\u4e00-\u9fa5]{2,30}?([((].{1,8}?[))])?[::]+[\\s\u3000\u2003\u00a0]*[\r\n])"+pgflag).FindAllStringSubmatchIndex(con, -1)
+		}
+		if len(keys) == 0 {
+			keys = regexp.MustCompile("()注[::]([\u4e00-\u9fa5]{2,8}?([((].{1,8}?[))])?[\\s\u3000\u2003\u00a0]*[\r\n])"+pgflag).FindAllStringSubmatchIndex(con, -1)
+		}
+		for _, key := range keys {
+			startEndMap[key[5]] = key[4]
+			//
+			headkey := con[key[4]:key[5]]
+			headkey = regReplAllSpace.ReplaceAllString(headkey, "")
+			if !regDivision.MatchString(headkey) {
+				headkey += ":"
+			}
+			headkey = moreColonReg.ReplaceAllString(headkey, ":")
+			colonIndexs := regDivision.FindAllStringIndex(headkey, -1)
+			if len(colonIndexs) > 1 {
+				headkey = headkey[colonIndexs[len(colonIndexs)-2][1]:colonIndexs[len(colonIndexs)-1][1]]
+			}
+			indexKeyStringMap[key[5]] = headkey
+			indexKeyIntMap[key[5]] = key[1]
+		}
+	}
+	indexs = getPkgIndex(indexs)
+	for ik, iv := range indexs {
+		if indexKeyStringMap[iv] != "" {
+			continue
+		}
+		if indexKeyIntMap[iv] == indexMap[iv] {
+			continue
+		}
+		if ik > 0 {
+			indexKeyStringMap[iv] = indexKeyStringMap[indexs[ik-1]]
+		}
+	}
+	//
+	//获取截取标识
+	surplusText, maxWarpCount, indexTextMap, indexWarpMap := interceptText(indexs, indexPkgMap, pkgIndexMap, startEndMap, con)
+	//查找分包内容,分kv
+	for _, iv := range indexs {
+		text := indexTextMap[iv]
+		//
+		warpIndex := regSpliteSegment.FindAllStringIndex(text, -1)
+		if len(indexWarpMap) > 0 {
+			maxWarpCount = indexWarpMap[iv]
+		}
+		if maxWarpCount > 0 && len(warpIndex) >= 5 && len(warpIndex) > maxWarpCount {
+			textTemp := text
+			text = textTemp[:warpIndex[maxWarpCount-1][1]]
+			surplusText += textTemp[warpIndex[maxWarpCount-1][0]:]
+		}
+		for bk, bv := range pkg {
+			//判断分包如果在这段文字里面,该段文字就属于该包的
+			if !strings.HasPrefix(text, bv[0]) {
+				continue
+			}
+			index := util.PackageNumberConvert(bk)
+			//去掉前缀,空格必须要加,分kv的时候要用
+			text = regexp.MustCompile(bv[0]+"[::]*").ReplaceAllString(text, "")
+			headKey := ""
+			if indexKeyStringMap[iv] != "" {
+				//if !filterPkgTitleKey.MatchString(indexKeyStringMap[iv]) {
+				headKey = indexKeyStringMap[iv]
+				text = indexKeyStringMap[iv] + "  " + text
+				//}
+				for _, pkgIndexMap_v := range pkgIndexMap[bv[0]] {
+					delete(indexKeyStringMap, pkgIndexMap_v)
+				}
+			}
+			//如果一块中有多个相同的包,合并到一个
+			if (*blockPackage)[index] != nil {
+				//合并文本
+				(*blockPackage)[index].Text += "\n" + text
+				//合并冒号kv
+				colonJobKv := GetKVAll(strings.TrimLeft(text, headKey), "", 1)
+				if headKey != "" {
+					kvAgain := GetKVAll(text, "", 4)
+					for kv_k, kv_v := range kvAgain.Kv {
+						if colonJobKv.Kv[kv_k] == "" {
+							colonJobKv.Kv[kv_k] = kv_v
+							colonJobKv.KvTag[kv_k] = kvAgain.KvTag[kv_k]
+						}
+					}
+				}
+				for kv_k, kv_v := range colonJobKv.Kv {
+					if kv_v == "" {
+						continue
+					}
+					if (*blockPackage)[index].ColonKV.Kv[kv_k] != "" {
+						continue
+					}
+					(*blockPackage)[index].ColonKV.Kv[kv_k] = kv_v
+				}
+				//合并空格kv
+				spaceJobKv := spacekvEntity.entrance(text, "")
+				for kv_k, kv_v := range spaceJobKv.Kv {
+					if kv_v == "" {
+						continue
+					}
+					if (*blockPackage)[index].SpaceKV.Kv[kv_k] != "" {
+						continue
+					}
+					(*blockPackage)[index].SpaceKV.Kv[kv_k] = kv_v
+				}
+			} else {
+				newBpkg := &util.BlockPackage{
+					Origin:   bk,
+					Text:     text,
+					Index:    index,
+					Type:     bv[1],
+					Accuracy: accuracy,
+				}
+				finalKv := GetKVAll(strings.TrimLeft(text, headKey), "", 4)
+				if headKey != "" {
+					kvAgain := GetKVAll(text, "", 4)
+					for kv_k, kv_v := range kvAgain.Kv {
+						if finalKv.Kv[kv_k] == "" {
+							finalKv.Kv[kv_k] = kv_v
+							finalKv.KvTag[kv_k] = kvAgain.KvTag[kv_k]
+						}
+					}
+				}
+				newBpkg.ColonKV = finalKv
+				newBpkg.SpaceKV = spacekvEntity.entrance(text, "")
+				(*blockPackage)[index] = newBpkg
+			}
+		}
+	}
+	//中标人排序
+	if isFindWinnerOrder && blockPackage != nil && len(*blockPackage) > 0 {
+		for _, v := range *blockPackage {
+			v.WinnerOrder = winnerOrderEntity.Find(v.Text, true, 2)
+		}
+	}
+	return true, surplusText
+}
+func getPkgIndex(indexs []int) []int {
+	sort.Ints(indexs)
+	indexsNew := []int{}
+	count := 0
+	for k, v := range indexs {
+		if k > 0 && v-indexs[k-1] <= 10 {
+			count++
+			continue
+		}
+		indexsNew = append(indexsNew, v)
+	}
+	if count > 0 && count == len(indexs)-1 {
+		return []int{}
+	}
+	return indexsNew
+}
+
+//每个包对应的结束位置,都是整行结束
+func interceptText(indexs []int, indexPkgMap map[int]string, pkgIndexMap map[string][]int, startEndMap map[int]int, con string) (string, int, map[int]string, map[int]int) {
+	//util.Debug(con)
+	surplusText := ""
+	indexTextMap := map[int]string{}
+	indexWarpMap := map[int]int{}
+	maxWarpCount := 0
+	for ik, iv := range indexs {
+		text := ""
+		if ik < len(indexs)-1 {
+			if startEndMap[indexs[ik+1]] != 0 {
+				text = con[iv:startEndMap[indexs[ik+1]]]
+			} else {
+				text = con[iv:indexs[ik+1]]
+			}
+		} else {
+			text = con[iv:]
+		}
+		indexTextMap[iv] = text
+		warpCount := len(regSpliteSegment.FindAllStringIndex(text, -1))
+		if warpCount > maxWarpCount {
+			maxWarpCount = warpCount
+		}
+		indexWarpMap[iv] = warpCount
+		if ik == 0 {
+			surplusText += con[:iv]
+		}
+	}
+	pkgLaw := ""
+	if len(pkgIndexMap) > 1 {
+		//有规律的出现 AB or ABAB
+		if pkgLaw == "" {
+			prevVal := ""
+			notRepeatCount, currentIndex, onceMax, allMax := 0, -1, 0, 0
+			indexMaxMap := map[int]int{}
+			for ik, iv := range indexs {
+				if notRepeatCount == len(pkgIndexMap) {
+					notRepeatCount = 0
+				}
+				if prevVal != indexPkgMap[iv] {
+					notRepeatCount++
+				} else {
+					notRepeatCount = -1
+					currentIndex = ik
+					break
+				}
+				prevVal = indexPkgMap[iv]
+				if notRepeatCount == len(pkgIndexMap) {
+					indexMaxMap[iv] = onceMax
+					onceMax = 0
+				}
+				if indexWarpMap[iv] > onceMax {
+					onceMax = indexWarpMap[iv]
+					allMax = onceMax
+				}
+				if ik == len(indexs)-1 && notRepeatCount != len(pkgIndexMap) {
+					notRepeatCount = -2
+					currentIndex = ik
+				}
+			}
+			//util.Debug(allMax, currentIndex, indexWarpMap, indexMaxMap)
+			if len(indexMaxMap) > 0 {
+				pkgLaw = "AB"
+				thisMax := 0
+				for ik := len(indexs) - 1; ik >= 0; ik-- {
+					iv := indexs[ik]
+					if currentIndex != -1 && ik >= currentIndex {
+						indexWarpMap[iv] = allMax
+						continue
+					}
+					if indexMaxMap[iv] > 0 {
+						thisMax = indexMaxMap[iv]
+					}
+					indexWarpMap[iv] = thisMax
+				}
+			}
+		}
+	}
+	if pkgLaw == "" {
+		indexWarpMap = map[int]int{}
+	}
+	//util.Debug(pkgLaw, maxWarpCount, indexTextMap, indexWarpMap)
+	return surplusText, maxWarpCount, indexTextMap, indexWarpMap
+}
+
+//分块之后的kv
+func kvAfterDivideBlock(text string, from int) []*util.Kv {
+	blocks, _ := DivideBlock(text, from)
+	kvs := []*util.Kv{}
+	for _, v := range blocks {
+		//util.Debug(v.Text)
+		//		for _, vvv := range v.ColonKV.Kvs {
+		//			util.Debug(vvv.Key, vvv.Value, vvv.Title)
+		//		}
+		kvs = append(kvs, v.ColonKV.Kvs...)
+	}
+	return kvs
+}

+ 26 - 0
src/jy/pretreated/kv_test.go

@@ -0,0 +1,26 @@
+package pretreated
+
+import (
+	"testing"
+)
+
+func Test_kv(t *testing.T) {
+	con := `工程编码:130301S16365-01-19 
+工程类别:公共房屋建筑 
+
+工程名称:秦皇岛市食品药品检验中心建设项目勘察 建设单位:秦皇岛市食品和市场监督管理局 投资性质:政府 所在地区:海港区 招标方式:公开招标 
+招标代理单位:秦皇岛泓远工程项目管理有限公司 
+中标单位:中冶沈勘秦皇岛设计研究院有限公司 
+中标顺序:
+第1顺序中标人	中冶沈勘秦皇岛设计研究院有限公司
+第2顺序中标人	秦皇岛市建筑设计院
+第3顺序中标人	河北宝地建设工程有限公司
+中标范围,内容:按要求完成本工程勘察所包含的全部内容 中标时间:2016-10-11 13:22(即北京时间:14:20)  公示时间:2016-10-12 
+中标价(元)/费率(%)(人民币:元):0 中标工期(天):20 
+项目经理姓名:王平 项目经理编码:AY131300747 质量承诺:合格 建筑面积(平米):498
+参加投标企业:秦皇岛市建筑设计院
+中冶沈勘秦皇岛设计研究院有限公司
+河北宝地建设工程有限公司
+ 备注:勘察浮动幅度值K为:0.20`
+	FindKv(con, "")
+}

+ 142 - 0
src/jy/pretreated/multipackage.go

@@ -0,0 +1,142 @@
+package pretreated
+
+import (
+	"regexp"
+	"sort"
+)
+
+var (
+	/**
+	监理 施工没有处理
+	**/
+	//替换容易混淆的词
+	PreReg  = regexp.MustCompile("(同|每|对|[^其]中|仅|分|任意)[一二三四五六七八九十\\d]个?(子|合同|分|施工|监理)?(标段?|包)|项目标号|文件A包|涉及包号|包件号?|标段(名称|编号)|0\\s?个标段|1\\-[\\d]标段|子包(\\d、)+\\d|\\d\\.\\d(标段|包)[^一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]|[1-9]标。")
+	PreReg1 = regexp.MustCompile("[^\n]([A-Z]?([一二三四五六七八九十]|\\d)、)+[A-Z]?([一二三四五六七八九十]|\\d)(标段?|包)")
+	//有分包划分情况的直接对比是1的肯定不是分包
+	PreCheckMulti = regexp.MustCompile("[^第]([一二三四五六七八九十两0-9ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)[  \u3000\u2003\u00a0]*个?((子|合同|分|施工|监理)?(标段?|包|合同段|标包))进行|(划分|分[设为成]?|共[分设有计]?)[::]?[  \u3000\u2003\u00a0]*([一二三四五六七八九十两0-9ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)[  \u3000\u2003\u00a0]*个?((子|合同|分|施工|监理)?(标段?|包|合同段|标包|项目))")
+	//替换容易混淆的词
+	PreCon  = regexp.MustCompile("([\r\n]|^)[\u3000\u2003\u00a0\\s]*(\\d\\.)+\\d|[一二三四五1-9、.]+[  \u3000\u2003\u00a0]*((标段|分包)(划分|情况)|(标书))|([上下]一[条页篇][::]?[^,,。\\n]{0,120}|备注[::][^\\n]{0,120}|业绩[::][^\\n,。,]{0,80}|三包(手册|服务|政策|凭证|期|标准|规定|责任|要求|售后)|(要求|提供|质量|国家|享受|负责|实行|执行|承诺|门前|法定|规定).{0,6}三包|“三包”|\\d+万?([个套只支分名][^标包])|[?]|[((]请?注意[::][^((]+[))])")
+	PreCon2 = regexp.MustCompile("[评中开定]\\s?标\\s?[0-9一二三四五六七八九十]+|标[准尺高书注]|[^中]标价|[开鼠投招军指企目]标|包[括含装为内]|[承树]包|CA证书")
+	//替换容易混淆的词
+	PreCon1 = regexp.MustCompile("(\\d+\\.?)+万?元")
+	//提取分包标识
+	MultiReg = regexp.MustCompile("[第]?([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ\\-]+)#?((子|合同|分|施工|监理)?(标段?|包|合同段|标包))|((子|分|合同|分|施工|监理)?(标|包件?)(段|号)?)[  \u3000\u2003\u00a0]*((\\d[.])+\\d|[一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ\\-]+)[::]?")
+	//匹配到的包格式分类统计
+	keyregs = []map[*regexp.Regexp]int{
+		map[*regexp.Regexp]int{
+			regexp.MustCompile("^[一二三四五六七八九十]+$"): 8,
+		},
+		map[*regexp.Regexp]int{
+			regexp.MustCompile("^[0-9]+$"): 7,
+		},
+		map[*regexp.Regexp]int{
+			regexp.MustCompile("^[A-Za-z]+[0-9]*$"): 6,
+		},
+	}
+	//冒号处理优先级高 如标段一:
+	MH = regexp.MustCompile("[::]")
+	//匹配包有的时候类似 包2LN2的只保留前面数字
+	ignoreReg = regexp.MustCompile("^(\\d+)[A-Za-z].*")
+	//标题中含分包特征的标段一、二、三或包1、包2之类,根据
+	TitleReg     = regexp.MustCompile("([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ、\\-~至]+(子|合同|分|施工|监理|标)?[包标段][号段]?[、]?)+|((子|合同|分|施工|监理|标)?[包标段][号段]?[一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ、\\-~至]+[、]?)+|(子|合同|分|施工|监理|标)?[包标段][号段]?[a-zA-Z0-9]+[\\-~-至、](子|合同|分|施工|监理|标)?[包标段][号段]?[a-zA-Z0-9]+")
+	clearPkgFlag = regexp.MustCompile("^[\\-]+|[\\-]+$")
+)
+
+//判断分包
+func CheckMultiPackage(con, title string) (content string, m map[string][]string, b bool) {
+	m = map[string][]string{}
+	//if TitleReg.MatchString(title) {
+	//log.Println(title+"\n------------------", TitleReg.FindAllStringSubmatch(title, -1))
+	//}
+	con = PreReg.ReplaceAllString(con, "")
+	con = PreReg1.ReplaceAllString(con, "")
+	pres := PreCheckMulti.FindStringSubmatch(con)
+	if len(pres) == 10 {
+		//log.Println(pres)
+		k := pres[1]
+		if k == "" {
+			k = pres[6]
+		}
+		if k == "1" || k == "一" {
+			return
+		} else {
+			//log.Println("all: ", k)
+		}
+	}
+	con = PreCheckMulti.ReplaceAllString(con, "")
+	con = PreCon.ReplaceAllString(con, "\n")
+	content = con
+	con = PreCon2.ReplaceAllString(con, "")
+	con = PreCon1.ReplaceAllString(con, "")
+	res := MultiReg.FindAllStringSubmatch(con, -1)
+	if len(res) > 0 { //5 6
+		mindex := map[string]int{}
+		for index, v := range res {
+			k := v[1]
+			vindex := 2
+			if k == "" {
+				k = v[9]
+				vindex = 5
+			}
+			if len(m[k]) == 0 && k != "" {
+				k = ignoreReg.ReplaceAllString(k, "$1")
+				k = clearPkgFlag.ReplaceAllString(k, "")
+				//log.Println(k, "----")
+				m[k] = []string{clearPkgFlag.ReplaceAllString(v[0], ""), v[vindex]}
+				mindex[k] = index
+			}
+		}
+		if len(m) > 1 {
+			//对k优先级进行处理过滤
+			SEL := -1 //确定以哪种类型为标段标识,没有去判断v相同不相同,存在一定的误判!如 1:合同包1 1:包
+			mapclassstr := map[int]map[string][]string{}
+			mapclass := map[int]int{}
+			for k, v := range m {
+				str := res[mindex[k]][0]
+				mk := 5
+				for _, keyreg := range keyregs {
+					for reg, pos := range keyreg {
+						if reg.MatchString(k) {
+							mk = pos
+							break
+						}
+					}
+				}
+				mapclass[mk]++
+				if mapclassstr[mk] == nil {
+					mapclassstr[mk] = map[string][]string{}
+				}
+				mapclassstr[mk][k] = []string{v[0], v[1]}
+				if MH.MatchString(str) { //如果有冒号直接确定
+					SEL = mk
+				}
+			}
+			//log.Println(mapclassstr, mapclass, SEL)
+			if SEL > 0 {
+				m = mapclassstr[SEL]
+			} else {
+				//比较出哪个最多,倒排,如果都一样,按 9 8 7 6 5来处理
+				max := 0
+				maxk := []int{}
+				for k, v := range mapclass {
+					if v > max {
+						max = v
+						maxk = []int{}
+						maxk = append(maxk, k)
+					} else if v == max {
+						maxk = append(maxk, k)
+					}
+				}
+				if len(maxk) > 0 {
+					sort.Ints(maxk)
+					m = mapclassstr[maxk[len(maxk)-1]]
+				}
+			}
+		}
+		if len(m) > 0 {
+			b = true
+		}
+		//log.Println(m, res)
+	}
+	return
+}

+ 78 - 0
src/jy/pretreated/spacekv.go

@@ -0,0 +1,78 @@
+//识别空格kv
+package pretreated
+
+import (
+	"jy/util"
+	"regexp"
+	"strings"
+)
+
+type SpacekvEntity struct{}
+
+var (
+	spacekvEntity   = &SpacekvEntity{}
+	filterLine      = regexp.MustCompile("[::,,。??'\"“”‘’·~!…+=|&*#$【】]")
+	filterSpaceKey  = regexp.MustCompile("[((][^((]+[))]")
+	excludeSpaceKey = regexp.MustCompile("[.、�\\[【{{〔<《\\]】}}〕>》]")
+)
+
+func (se *SpacekvEntity) entrance(text, title string) *util.JobKv {
+	lines := se.getLines(text)
+	kvMaps := []*util.Kv{}
+	for _, line := range lines {
+		kvMap := se.divideKV(line)
+		if kvMap == nil {
+			continue
+		}
+		kvMaps = append(kvMaps, kvMap...)
+	}
+	//FormatContactKv(&kvMaps, title, nil, contactFormat)
+	kv, tagKv := KvTagsToKV(kvMaps, title, nil, 1)
+	return &util.JobKv{
+		Kvs:   kvMaps,
+		Kv:    kv,
+		KvTag: tagKv,
+	}
+}
+
+//空格分kv
+func (se *SpacekvEntity) divideKV(line string) []*util.Kv {
+	line = strings.TrimSpace(line)
+	line = regReplAllSpace.ReplaceAllString(line, " ")
+	line = TimeHM.ReplaceAllString(line, "D$1H$2M")
+	if line == "" || strings.Count(line, " ") == 0 || filterLine.MatchString(line) {
+		return nil
+	}
+	kv := strings.Split(line, " ")
+	kvs := []*util.Kv{}
+	for i := 0; i+1 <= len(kv)-1; i = i + 2 {
+		k, v := kv[i], kv[i+1]
+		k = filterSpaceKey.ReplaceAllString(k, "")
+		//key字数限制
+		if len([]rune(k)) <= 1 || len([]rune(k)) > 15 {
+			continue
+		}
+		//过滤key
+		if excludeSpaceKey.MatchString(k) {
+			continue
+		}
+		kvs = append(kvs, &util.Kv{Key: k, Value: v})
+	}
+	return kvs
+}
+
+//分段
+func (se *SpacekvEntity) getLines(text string) []string {
+	lines := strings.FieldsFunc(text, func(r rune) bool {
+		return r == 10 || r == 13
+	})
+	arrays := []string{}
+	for _, line := range lines {
+		line = regTrimSpace.ReplaceAllString(line, "")
+		if line == "" {
+			continue
+		}
+		arrays = append(arrays, line)
+	}
+	return arrays
+}

Разлика између датотеке није приказан због своје велике величине
+ 94 - 0
src/jy/pretreated/tablev1_test.go


+ 777 - 0
src/jy/pretreated/tablev2.go

@@ -0,0 +1,777 @@
+package pretreated
+
+//定义表格对象
+
+import (
+	"fmt"
+	u "jy/util"
+	qutil "qfw/util"
+	"regexp"
+	"strings"
+	"sync"
+
+	"github.com/PuerkitoBio/goquery"
+)
+
+//所有中标候选人只取第一个
+type TableResult struct {
+	Id             interface{} //信息id
+	Toptype        string      //信息类型
+	Itype          int         //1全文 2是块
+	BlockTag       string      //块标签
+	Html           string
+	Tabs           []*Table             //子表集合,子表中包含标准化kv或原始kv
+	GoqueryTabs    []*goquery.Selection //goquery对象
+	TableSize      int                  //子表的个数0,1,n
+	IsMultiPackage bool                 //是否有子包
+	PackageMap     *SortMap             //子包对象的sortmap,含标准化过的
+	SortKV         *SortMap             //全局KVmap值,标准化处理过的
+	SortKVWeight   map[string]int       //全局KVmap值,标准化处理过的
+	WinnerOrder    []map[string]interface{}
+}
+
+//快速创建TableResult对象
+func NewTableResult(Id interface{}, Toptype, BlockTag, con string, Itype int) *TableResult {
+	return &TableResult{
+		Id:           Id,
+		Toptype:      Toptype,
+		Html:         con,
+		Itype:        Itype,
+		BlockTag:     BlockTag,
+		Tabs:         []*Table{},
+		GoqueryTabs:  []*goquery.Selection{},
+		PackageMap:   NewSortMap(),
+		SortKV:       NewSortMap(),
+		SortKVWeight: map[string]int{},
+	}
+}
+
+//td节点
+type TD struct {
+	Goquery        *goquery.Selection //文本对象
+	TR             *TR                //所属TR对象
+	LeftNode       *TD                //左临节点
+	TopNode        *TD                //上临节点
+	RightNode      *TD                //右节点
+	BottomNode     *TD                //下节点
+	Val            string             //值
+	Text           string             //原始串
+	SortKV         *SortMap           //存放kv值
+	Html           string
+	BH             bool         //是否是表头
+	MustBH         bool         //不能修改的表头
+	StandardKey    string       //标准表头
+	Colspan        int          //合并列
+	Rowspan        int          //合并行
+	StartCol       int          //起始列
+	EndCol         int          //终止列
+	StartRow       int          //起始行
+	EndRow         int          //终止行
+	ColPos         int          //当前在TR中的位置
+	HeadTd         *TD          //(是val元素)k节点
+	KVDirect       int          //键-值方向,0未知,1横 2纵//指值和k的方向
+	KeyDirect      int          //k方向,k纵值横,k横值纵 1横 2纵
+	SonTds         []*TD        //(是key元素)值节点数组
+	SonTableResult *TableResult //子值表格集
+	ArrVal         []string     //数组值,当是左临元素是合并行的元素时!
+	Valtype        string       //"BO=中标人顺序"
+}
+
+var submatchreg = regexp.MustCompile(`((?:[一二三四五六七八九十0-10]+[、])([\\S]{4,12})|([\\S]{2,12}))[::]([\\S]{5,60})([一二三四五六七八九]+[、])?`)
+var BHKey = regexp.MustCompile(`^[^,,;:。、.]{2,8}.{0,3}[::].+$`)
+var dwReg = regexp.MustCompile("单位[::/ \\s\u3000\u2003\u00a0\\n]*([万亿元]+)")
+
+func NewTD(Goquery *goquery.Selection, tr *TR, table *Table) *TD {
+	defer qutil.Catch()
+	td := &TD{
+		ArrVal:  []string{},
+		Goquery: Goquery,
+		SonTds:  []*TD{},
+		TR:      tr,
+		SortKV:  NewSortMap(),
+	}
+	colspan, rowspan := 0, 0
+	col, bcol := td.Goquery.Attr("colspan")
+	if bcol {
+		colspan = qutil.IntAllDef(col, 1)
+	}
+	if colspan == 0 {
+		colspan = 1
+	}
+	row, brow := td.Goquery.Attr("rowspan")
+	if brow {
+		rowspan = qutil.IntAllDef(row, 1)
+	}
+	if rowspan == 0 {
+		rowspan = 1
+	}
+	td.Colspan, td.Rowspan = colspan, rowspan
+	td.Html, _ = td.Goquery.Html()
+	ht := td.Goquery.ChildrenFiltered("table")
+	bsontable := false
+	txt := ""
+	if ht.Size() > 0 {
+		txt = TextAfterRemoveTable(td.Html)
+		ts := td.TR.Table.TableResult
+		tabs, _ := ComputeConRatio(td.Html, 2)
+		if len(tabs) > 0 {
+			bsontable = true
+			stag := ts.BlockTag
+			if stag == "" {
+				var tdleft *TD
+				if len(tr.TDs) > 0 {
+					tdleft = tr.TDs[len(tr.TDs)-1]
+					if tdleft.BH {
+						//u.Debug(tdleft.Val),如果不存在就是上一行的
+						stag = tdleft.Val
+					}
+				} else if len(tr.Table.TRs) > 0 {
+					lasttr := tr.Table.TRs[len(tr.Table.TRs)-1]
+					str := ""
+					for _, td3 := range lasttr.TDs {
+						str += td3.Val
+						if len([]rune(str)) > 14 {
+							str = ""
+							break
+						}
+					}
+					stag = str
+				}
+			}
+			sonts := AnalyTableV2(tabs, ts.Toptype, stag, td.Html, 2, ts.Id)
+			td.BH = false
+
+			td.SonTableResult = sonts
+			//for _, k := range sonts.SortKV.Keys {
+			//u.Debug(k, sonts.SortKV.Map[k])
+			//				td.TR.Table.StandKV[k] = sonts.SortKV.Map[k].(string)
+			//				td.TR.Table.StandKVWeight[k] = sonts.SortKVWeight[k]
+			//}
+			if sonts.WinnerOrder != nil && len(sonts.WinnerOrder) > 0 {
+				td.TR.Table.TableResult.WinnerOrder = sonts.WinnerOrder
+			}
+			if sonts.IsMultiPackage {
+				td.TR.Table.BPackage = true
+				tb1 := td.TR.Table.BlockPackage
+				for k, v := range sonts.PackageMap.Map {
+					v1 := v.(*u.BlockPackage)
+					if tb1.Map[k] == nil {
+						tb1.AddKey(k, v)
+					} else {
+						bp := tb1.Map[k].(*u.BlockPackage)
+						if v1.TableKV != nil && v1.TableKV.Kv != nil {
+							for k2, v2 := range v1.TableKV.Kv {
+								if bp.TableKV.Kv[k2] == "" {
+									bp.TableKV.Kv[k2] = v2
+									bp.Text += fmt.Sprintf("%v:%v\n", k2, v2)
+								}
+							}
+						}
+					}
+				}
+
+				//u.Debug(fmt.Sprintf("%v", td.TR.Table.BlockPackage.Map["1"]))
+
+			}
+		}
+	} else {
+		txt = td.Goquery.Text()
+	}
+	text := dwReg.ReplaceAllString(u.TrimLRAll(txt, ""), "$1")
+	//u.Debug(txt, text)
+	td.Val = text
+	td.Text = txt
+	//对td单元格值判断是否是key
+	lentxt := len([]rune(text))
+	//if lentxt > 9 {
+	//td.KV = GetKVAll(txt, "")
+	ub := []*u.Block{}
+	if lentxt > 50 { //看是否划块
+		//u.Debug(txt)
+		ub, _ = DivideBlock(txt, 2)
+		if len(ub) > 0 {
+			colonKvWeight := map[string]int{}
+			spaceKvWeight := map[string]int{}
+			for _, bl := range ub {
+				//冒号kv
+				for bl_ck, bl_cv := range bl.ColonKV.Kv {
+					//u.Debug(bl_ck, bl_cv)
+					if td.SortKV.Map[bl_ck] == nil || bl.ColonKV.KvTag[bl_ck].Weight >= colonKvWeight[bl_ck] {
+						colonKvWeight[bl_ck] = bl.ColonKV.KvTag[bl_ck].Weight
+						td.SortKV.AddKey(bl_ck, bl_cv)
+					}
+				}
+				//空格kv
+				for bl_sk, bl_sv := range bl.SpaceKV.Kv {
+					if td.SortKV.Map[bl_sk] == nil || bl.SpaceKV.KvTag[bl_sk].Weight >= spaceKvWeight[bl_sk] {
+						spaceKvWeight[bl_sk] = bl.SpaceKV.KvTag[bl_sk].Weight
+						td.SortKV.AddKey(bl_sk, bl_sv)
+					}
+				}
+			}
+		}
+		//
+		blockPackage := map[string]*u.BlockPackage{}
+		isFindPkg := true
+		/*if td.ColPos-1 >= 0 && excludeKey.MatchString(tr.TDs[td.ColPos-1].Text) {
+			isFindPkg = false
+		} else if len(tr.TDs) > 0 {
+			tdleft = tr.TDs[len(tr.TDs)-1]
+			if tdleft.BH && excludeKey.MatchString(tr.TDs[td.ColPos-1].Text) {
+				isFindPkg = false
+			}
+		}*/
+		if len(tr.TDs) > 0 {
+			tdleft := tr.TDs[len(tr.TDs)-1]
+			if tdleft.BH && excludeKey.MatchString(tdleft.Text) {
+				isFindPkg = false
+			}
+		}
+		if isFindPkg {
+			if len(ub) > 0 {
+				blockPackage = FindPackageFromBlocks(&ub, "")
+			} else {
+				blockPackage = FindPackageFromText("", text)
+			}
+		}
+		if len(blockPackage) > 0 {
+			table.BPackage = true
+			for bp_k, bp_v := range blockPackage {
+				var bp *u.BlockPackage
+				if table.TableResult.PackageMap.Map[bp_k] == nil {
+					bp = bp_v
+				} else {
+					bp = table.TableResult.PackageMap.Map[bp_k].(*u.BlockPackage)
+					bp.Text += "\n" + bp_v.Text
+				}
+				if bp.TableKV == nil {
+					bp.TableKV = u.NewJobKv()
+				}
+				for k2, v2 := range bp_v.ColonKV.Kv {
+					if bp.TableKV.Kv[k2] == "" {
+						bp.TableKV.Kv[k2] = v2
+					}
+				}
+				for k2, v2 := range bp_v.SpaceKV.Kv {
+					if bp.TableKV.Kv[k2] == "" {
+						bp.TableKV.Kv[k2] = v2
+					}
+				}
+				table.TableResult.PackageMap.Map[bp_k] = bp
+			}
+		}
+	}
+	//
+	if lentxt < 50 {
+		//		td.SortKV = FindKv(text, "")
+		kvTitle := ""
+		if len(td.TR.TDs) > 0 {
+			kvTitle = td.TR.TDs[len(td.TR.TDs)-1].Val
+		}
+		_, resm := colonkvEntity.entrance(text, kvTitle, 2)
+		for k, v := range resm {
+			td.SortKV.AddKey(k, v)
+		}
+		//u.Debug(td.SortKV.Keys, "-------2--------------------------------")
+		//		td.SortKV = FindKv(text, "") //GetKvFromtxt(text, "")
+		//resm := GetKVAll(text, "")
+		if len(td.SortKV.Keys) > 0 {
+			//td.KVDirect = 3 //不当头也不当值,忽略
+			if len(td.SortKV.Keys) == 1 && BHKey.MatchString(td.Val) && !MultiReg.MatchString(td.Val) {
+				td.Val = td.SortKV.Keys[0]
+				td.BH = true
+			}
+		} else if !bsontable {
+			txt := repSpace.ReplaceAllString(text, "")
+			btw, must, _, _, repl := CheckHeader(txt)
+			td.Valtype = repl
+			td.MustBH = must
+			td.BH = btw
+		}
+	} else if len(ub) == 0 { //之前这里没加判断,现在加上判断,造成分块之后的kv被覆盖掉
+		//u.Debug("----\n\n\n", txt, "\n\n\n----")
+		//u.Debug(GetKVAll(txt, ""))
+		/*
+			subVal := submatchreg.FindAllStringSubmatch(txt, -1)
+			if len(subVal) > 0 {
+				for _, subv1 := range subVal {
+					if len(subv1) == 6 {
+						tr.Table.SortKV.AddKey(If(subv1[2] == "", subv1[3], subv1[2]).(string), subv1[4])
+						//tr.Table.SortKV.AddKey(subv1[1], subv1[2])
+					}
+				}
+			}
+		*/
+
+		td.SortKV = FindKv(text, "", 2)
+
+		//		td.LeftNode.Val
+		//		for _, vvv := range *td.TR {
+		//			u.Debug(">>>>>")
+		//		}
+		kvTitle := ""
+		if len(td.TR.TDs) > 0 {
+			kvTitle = td.TR.TDs[len(td.TR.TDs)-1].Val
+		}
+		_, resm := colonkvEntity.entrance(text, kvTitle, 2)
+		for k, v := range resm {
+			td.SortKV.AddKey(k, v)
+		}
+	}
+	bhead := false
+	if td.TR.RowPos == 0 { //第一行
+		if td.Goquery.Closest("thead").Size() == 1 && !bsontable { //如果是thead确定为k值表头
+			bhead = true
+		}
+	}
+	if bhead && !bsontable {
+		td.BH = true
+		td.KeyDirect = 1
+		td.KVDirect = 2
+	}
+	//u.Debug(td.BH, td.Val)
+	return td
+}
+func (t *Table) Print() {
+	for row, trs := range t.TRs {
+		for col, td := range trs.TDs {
+			u.Debug(row, col, td.Val, td.BH, td.SortKV.Map)
+		}
+	}
+}
+
+type TR struct {
+	TDs      []*TD
+	TopTR    *TR    //上临行
+	BottomTR *TR    //下临行
+	Table    *Table //所属表格对象
+	RowPos   int    //当前在第几行
+	//-----计算
+	MaxRow       int  //最大跨行 Max(td.StartRow-td.EndRow)
+	MinRow       int  //最小跨行
+	StartRow     int  //起始行
+	EndRow       int  //结束行
+	MaxCol       int  //最大列
+	MinCol       int  //最小列
+	StartCol     int  //起始列
+	EndCol       int  //结束列
+	BDiffSpanRow bool //起始行,行中有没有不同跨行  - - - = -
+	BDiffSpanCol bool //起始列,列中有没有不同跨列   |
+}
+
+func NewTR(Table *Table) *TR {
+	return &TR{
+		TDs:   []*TD{},
+		Table: Table,
+	}
+}
+func (tr *TR) AddTD(td *TD) {
+	/**对跨行没有意义
+	if len(tr.TDs) > 0 {
+		td.LeftNode = tr.TDs[len(tr.TDs)-1]
+		tr.TDs[len(tr.TDs)-1].RightNode = td
+	}
+	**/
+	td.ColPos = len(tr.TDs)
+	tr.TDs = append(tr.TDs, td)
+}
+
+/*--  START --- 处理表头概率开始 -------*/
+type pos struct {
+	Max int
+	Min int
+}
+
+type TDRationScope struct {
+	Rationmap map[*pos]float32
+	Tdmap     map[*pos][]*TD
+	Poss      []*pos
+	Parentkey string
+}
+
+func NewTDRationScope(key string) *TDRationScope {
+	return &TDRationScope{map[*pos]float32{}, map[*pos][]*TD{}, []*pos{}, key}
+}
+
+func (tdr *TDRationScope) GetPos(td *TD) (poss *pos) {
+	k1 := tdr.Parentkey[:1]
+	m1, m2 := td.StartRow, td.EndRow
+	if k1 == "r" {
+		m1, m2 = td.StartCol, td.EndCol
+	}
+	for _, v := range tdr.Poss {
+		if v.Max >= m2 && v.Min <= m1 {
+			poss = v
+			return
+		}
+	}
+	return
+}
+
+func (tdr *TDRationScope) GetTDRation(td *TD) (ration float32, tds []*TD) {
+	poss := tdr.GetPos(td)
+	if poss != nil {
+		ration = tdr.Rationmap[poss]
+		tds = tdr.Tdmap[poss]
+	}
+	return
+}
+
+func (tdr *TDRationScope) Addtd(td *TD) {
+	k1 := tdr.Parentkey[:1]
+	m1, m2 := td.StartRow, td.EndRow
+	if k1 == "r" {
+		m1, m2 = td.StartCol, td.EndCol
+	}
+	bfind := false
+	for _, v := range tdr.Poss {
+		if m1 == v.Max+1 { //找到
+			bfind = true
+			v.Max = m2
+			tdr.Tdmap[v] = append(tdr.Tdmap[v], td)
+			break
+		}
+	}
+	if !bfind {
+		pos1 := &pos{m2, m1}
+		tdr.Tdmap[pos1] = []*TD{td}
+		tdr.Poss = append(tdr.Poss, pos1)
+	}
+}
+
+/*-- END --- 处理表头概率 -------*/
+
+//table表格
+type Table struct {
+	Brule                  bool //是否规则
+	TRs                    []*TR
+	BFirstRow              bool
+	RowNum                 int                       //数行
+	ColNum                 int                       //列数
+	TDNum                  int                       //td个数
+	BPackage               bool                      //是否有包
+	SortKV                 *SortMap                  //带排序的KV值
+	StandKV                map[string]string         //过滤后的标准化kv
+	StandKVWeight          map[string]int            //过滤后的标准化kv
+	StandRuleKV            map[string]string         //过滤后的规则kv
+	kvscope                map[int]map[int][]*TD     //sortkey第几个元素的的第几个值的结束位置
+	kTD                    map[int]*TD               //根据索引找到key的TD元素
+	SonTables              []*Table                  //孩子表集合
+	Tag                    string                    //表格的标签
+	Desc                   string                    //表格描述内容
+	Goquery                *goquery.Selection        //表格的goquery对象
+	Html                   string                    //所属的文本内容
+	BlockPackage           *SortMap                  //子包数组
+	TableResult            *TableResult              //父元素
+	StartAndEndRation      map[string]*TDRationScope //同行或同列的概率,截断的单独起算
+	StartAndEndRationKSort *SortMap
+	WinnerOrder            []map[string]interface{}
+	BSplit                 bool //是否是有一个表拆分成的多个表
+	BHeader                bool //拆分表是否有表头
+}
+
+func NewTable(Html string, TableResult *TableResult, tab *goquery.Selection) *Table {
+	return &Table{
+		Html:                   Html,
+		SortKV:                 NewSortMap(),
+		StandKV:                map[string]string{},
+		StandKVWeight:          map[string]int{},
+		kvscope:                map[int]map[int][]*TD{},
+		kTD:                    map[int]*TD{},
+		SonTables:              []*Table{},
+		Goquery:                tab,
+		TRs:                    []*TR{},
+		TableResult:            TableResult,
+		StartAndEndRation:      map[string]*TDRationScope{},
+		StartAndEndRationKSort: NewSortMap(),
+		BlockPackage:           NewSortMap(),
+	}
+}
+
+func (t *Table) AddTR(tr *TR) {
+	if len(tr.TDs) > 0 {
+		if len(t.TRs) > 0 {
+			tr.TopTR = t.TRs[len(t.TRs)-1]
+			t.TRs[len(t.TRs)-1].BottomTR = tr
+		}
+		tr.RowPos = len(t.TRs)
+		t.TRs = append(t.TRs, tr)
+	}
+}
+
+func (t *Table) InsertTR(tr *TR) {
+	if len(tr.TDs) > 0 {
+		if len(t.TRs) > 0 {
+			t.TRs[0].TopTR = tr
+		}
+		tr.RowPos = 0
+		for _, _tr := range t.TRs {
+			_tr.RowPos += 1
+		}
+		t.TRs = append([]*TR{tr}, t.TRs...)
+	}
+}
+
+//支持排序的map
+type SortMap struct {
+	Index map[string]int
+	Keys  []string
+	Map   map[string]interface{}
+	Lock  sync.Mutex
+}
+
+//快速创建排序map
+func NewSortMap() *SortMap {
+	return &SortMap{
+		Index: map[string]int{},
+		Keys:  []string{},
+		Map:   map[string]interface{}{},
+	}
+}
+
+//增加值
+var nullVal = regexp.MustCompile("^[/无,.。;、]+$|^详见.{2,8}$")
+
+func (s *SortMap) AddKey(key string, val interface{}) {
+	//判断val
+	if v, ok := val.(string); ok && nullVal.ReplaceAllString(u.TrimLRSpace(v, ""), "") == "" {
+		return
+	}
+	s.Lock.Lock()
+	defer s.Lock.Unlock()
+	//重复
+	if s.Map[key] == nil {
+		s.Index[key] = len(s.Keys)
+		s.Keys = append(s.Keys, key)
+	}
+	s.Map[key] = val
+}
+
+//增加值
+func (s *SortMap) ReplaceKey(key string, val interface{}, replacekey string) {
+	s.Lock.Lock()
+	defer s.Lock.Unlock()
+	//重复
+	v := s.Index[replacekey]
+	s.Index[key] = v
+	delete(s.Index, replacekey)
+	s.Keys = append(s.Keys[:v], append([]string{key}, s.Keys[v+1:]...)...)
+	delete(s.Map, replacekey)
+	s.Map[key] = val
+}
+
+//删除值
+func (s *SortMap) RemoveKey(key string) {
+	s.Lock.Lock()
+	defer s.Lock.Unlock()
+	delete(s.Map, key)
+	pos := s.Index[key]
+	delete(s.Index, key)
+	if len(s.Keys) > 0 {
+		s.Keys = func() []string {
+			newkeys := []string{}
+			if len(s.Keys) > 1 {
+				if pos == 0 {
+					newkeys = append(newkeys, s.Keys[1:]...)
+					//每一个都减一
+					for k, v := range s.Index {
+						s.Index[k] = v - 1
+					}
+				} else if pos == len(s.Keys) {
+					newkeys = append(newkeys, s.Keys[:pos]...)
+				} else {
+					tmp := s.Keys[pos+1:]
+					newkeys = append(append(newkeys, s.Keys[:pos]...), tmp...)
+					for _, v := range tmp {
+						s.Index[v] -= 1
+					}
+				}
+			}
+			return newkeys
+		}()
+	}
+}
+
+//判断表头是key的对象
+type TableKeyV1 struct {
+	TMap        map[string]interface{}
+	TReg        []*regexp.Regexp
+	TRegReplStr []string
+}
+
+//判断表头时用到的顺序 正文、结果表头、正常表头
+var THeadStr = []string{
+	"con",
+	"jghead",
+	"normalhead",
+}
+
+//存放敏感词
+var TKMaps = map[string]*TableKeyV1{}
+
+//过滤所有非汉字内容
+var filterThText = regexp.MustCompile("([((【\\[].*[))】\\]])|([^0-9a-zA-Z\\p{Han}]+)")
+var tLock = sync.Mutex{}
+
+//matchStro为tablev1.json文件中的key,txt为表格的内容也可以是表格的标签
+//主要实现表格是否是表头的判断,表格是否有用的判断(如人员情况等是无用的)
+func CheckCommon(txt string, matchStr ...string) (res, must bool, stype, reg, repl string) {
+	txt = filterThText.ReplaceAllString(txt, "")
+	stype = "con"
+	if len([]rune(txt)) < 30 {
+		tLock.Lock()
+		defer tLock.Unlock()
+		if len(TKMaps) == 0 {
+			for k, v := range u.TableK1 {
+				tk := &TableKeyV1{
+					map[string]interface{}{},
+					[]*regexp.Regexp{},
+					[]string{},
+				}
+				thMap := map[string]interface{}{}
+				for _, v1 := range v {
+					v1s := strings.Split(v1, "__")
+					if len(v1s) == 2 {
+						tk.TReg = append(tk.TReg, regexp.MustCompile(v1s[0]))
+						tk.TRegReplStr = append(tk.TRegReplStr, v1s[1])
+					} else {
+						key := v1
+						nowMap := &thMap
+						for i := 0; i < len(key); i++ {
+							kc := key[i : i+1]
+							if v, ok := (*nowMap)[kc]; ok {
+								nowMap, _ = v.(*map[string]interface{})
+							} else {
+								newMap := map[string]interface{}{}
+								newMap["Y"] = "0"
+								(*nowMap)[kc] = &newMap
+								nowMap = &newMap
+							}
+							if i == len(key)-1 {
+								(*nowMap)["Y"] = "1"
+								(*nowMap)["K"] = key
+								//(*nowMap)["V"] = v
+							}
+						}
+					}
+				}
+				tk.TMap = thMap
+				TKMaps[k] = tk
+			}
+		}
+		//先正则、后子串查找
+	L1:
+		for _, v := range matchStr {
+			//u.Debug(v)
+			for n, vreg := range TKMaps[v].TReg {
+				if vreg.MatchString(txt) {
+					//u.Debug(txt, v, vreg.String())
+					reg = vreg.String()
+					repl = TKMaps[v].TRegReplStr[n]
+					if v != "con" {
+						res = true
+						if "M" == repl {
+							must = true
+						}
+					}
+					stype = v
+					break L1
+				}
+			}
+			//以下是敏感词子串查找匹配
+			pos := 0
+			thMap := TKMaps[v].TMap
+			nowMap := &thMap
+			for i := 0; i < len(txt); i++ {
+				word := txt[i : i+1]
+				nowMap, _ = (*nowMap)[word].(*map[string]interface{})
+				if nowMap != nil { // 存在,则判断是否为最后一个
+					if pos == 0 {
+						pos = i
+					}
+					if "1" == qutil.ObjToString((*nowMap)["Y"]) {
+						if v != "con" {
+							res = true
+						}
+						stype = v
+						pos = 0
+						break L1
+					}
+				} else {
+					nowMap = &thMap
+					if pos > 0 {
+						i = pos
+						pos = 0
+					}
+				}
+			}
+		}
+		return
+	} else {
+		return
+	}
+}
+
+//根据td中的内容验证表头,根据tablev1.json中配置的三种规则(含正则和子串查找算法)
+func CheckHeader(txt string) (res, must bool, stype, reg, repl string) {
+	return CheckCommon(txt, THeadStr...)
+}
+
+/**
+计算表格占比,返回表格数组、占比
+con 文本
+strtype 1全文 2块文本
+**/
+func ComputeConRatio(con string, strtype int) (tabs []*goquery.Selection, ratio float32) {
+	defer qutil.Catch()
+	doc, _ := goquery.NewDocumentFromReader(strings.NewReader(con))
+	tables := doc.Find("table")
+	if tables.Size() > 0 {
+		tabs = []*goquery.Selection{}
+		for i := 0; i < tables.Size(); i++ {
+			tmpt := tables.Eq(i)
+			b := false
+			for j := 0; j < len(tabs); j++ {
+				if tabs[j].Contains(tmpt.Get(0)) {
+					b = true
+				}
+			}
+			if !b {
+				tabs = append(tabs, tmpt)
+			}
+		}
+		tlen := 0
+		cons := doc.Text()
+		for _, t := range tabs {
+			tlen += len(t.Text())
+		}
+		ratio = float32(tlen) / float32(len(cons))
+	}
+	/**
+	if ratio < float32(0.992) {
+		//取出排除表格之外的文本
+		txt =getTextAfterRemoveTable(con)
+	}
+	**/
+	return
+}
+
+//取出排除表格之外的文本
+func TextAfterRemoveTable(con string) string {
+	doc2, _ := goquery.NewDocumentFromReader(strings.NewReader(con))
+	doc2.Find("table").Remove()
+	return doc2.Text()
+}
+
+func HtmlAfterRemoveTable(con string) string {
+	doc2, _ := goquery.NewDocumentFromReader(strings.NewReader(con))
+	doc2.Find("table").Remove()
+	html, _ := doc2.Html()
+	return html
+}
+func If(condition bool, trueVal, falseVal interface{}) interface{} {
+	if condition {
+		return trueVal
+	}
+	return falseVal
+}

+ 385 - 0
src/jy/pretreated/winnerorder.go

@@ -0,0 +1,385 @@
+package pretreated
+
+import (
+	"clear"
+	"jy/util"
+	qutil "qfw/util"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+type WinnerOrderEntity struct {
+}
+type WinnerFlag struct {
+	index     int //数组索引
+	start     int //数组开始索引
+	end       int //数组结束索引
+	textStart int //文本开始索引
+	textEnd   int //文本结束索引
+	max       int
+	indexs    []int
+}
+
+var (
+	winnerOrderEntity = &WinnerOrderEntity{}
+	numberReg         = regexp.MustCompile("[一二三四五六七八九十0-9]+")
+	thisNumberReg     = regexp.MustCompile("第" + numberReg.String())
+	winnerReg1        = regexp.MustCompile("(^|[^为])(【?(推荐)?第[一二三四五六七八九十1-9]+(合格|名|包|标段)?】?((候|侯)选)?(备选|成交|中(标|选))人?([((]成交[))])?((候|侯)选|排序)?(人(单位)?|供应商|单位|机构)(名称)?为?)($|[^,;;。,])")
+	winnerReg2        = regexp.MustCompile("(排名第[一二三四五六七八九十1-9]+|第[一二三四五六七八九十1-9]+(候|侯)选人)")
+	winnerReg3        = regexp.MustCompile("(第[一二三四五六七八九十1-9]+名)")
+	winnerReg4        = regexp.MustCompile("((确认|推荐|评审|排(名|序))[为::]+|(由高到低排序前.名|公示下列内容|(确定|推荐)的?中(标|选)候选人|\n中(标|选)候选.{1,3}\\s*\n|\n(中(标|选)候选.{1,3}[::\u3000\u2003\u00a0\\s]|成交候选供应商)|(排(名|序)|公(示|告)|具体|推荐|结果(公示)?|中(标|选)候选人.{0,2})如下|[一二三四五六七八九十\\d]+、(中(标|选)候选[^\n::]{1,8}|.{0,8}(成交|结果)信息|成交[^\n::]{2,8}))[为::]?)")
+	winnerReg5        = regexp.MustCompile("([^,;;。,、\n]+)(为?第[一二三四五六七八九十1-9]+(成交|中标)?((候|侯)选(人|供应商|单位|机构)|名)|排名第[一二三四五六七八九十1-9]+)([,;;。,、]|\\s+\n)")
+	colonEndReg       = regexp.MustCompile("[::]$")
+	toWarpReg         = regexp.MustCompile("[,。,;;]+")
+	findamountReg     = regexp.MustCompile("[,。,;;\u3000\u2003\u00a0\\s]+")
+	amountReg         = regexp.MustCompile("^\\d+(\\.\\d+)?((百|千)?元|(百|千)?(万|亿)元?)$")
+	companyWarpReg    = regexp.MustCompile("(公司)(.+?[::])")
+	findCompanyReg    = regexp.MustCompile("[^::]+公司")
+	colonSpaceReg     = regexp.MustCompile("[::]\\s+")
+)
+
+/*
+ *查找分包中的中标人排序
+ *text文本,flag非否精确查找
+ *from 来源
+ */
+func (wo *WinnerOrderEntity) Find(text string, flag bool, from int) []map[string]interface{} {
+	text = winnerReg5.ReplaceAllString(text, "\n$2:$1\n")
+	if strings.TrimSpace(text) == "" {
+		return []map[string]interface{}{}
+	}
+	blocks := winnerReg4.Split(text, -1)
+	if len(blocks) == 0 {
+		blocks = append(blocks, text)
+	}
+	winners := wo.findByReg(text, blocks, winnerReg1, from)
+	if len(winners) == 0 {
+		winners = wo.findByReg(text, blocks, winnerReg2, from)
+	}
+	if len(winners) == 0 {
+		if flag {
+			winners = wo.findByReg(text, blocks, winnerReg3, from)
+		} else {
+			indexs_4 := winnerReg4.Split(text, -1)
+			if len(indexs_4) > 1 {
+				for _, v_4 := range indexs_4 {
+					indexs_3 := winnerReg3.FindAllStringIndex(v_4, -1)
+					if len(indexs_3) < 2 {
+						continue
+					}
+					for _, v_3 := range indexs_3 {
+						if strings.Count(v_4[:v_3[1]], "\n") <= 3 {
+							winners = wo.findByReg(text, blocks, winnerReg3, from)
+							break
+						}
+					}
+				}
+			}
+		}
+	}
+	return winners
+}
+
+//获取中标人排序文本
+func (wo *WinnerOrderEntity) getText(text string, blocks []string, reg_2 *regexp.Regexp, from int) string {
+	isWinnerReg1 := reg_2 == winnerReg1
+	//确定中标候选人排序在哪个块里面
+	rightIndex, prevMax := -1, -1
+	var winnerFlag *WinnerFlag
+	for b_k, b_v := range blocks {
+		indexs := []*WinnerFlag{}
+		array := reg_2.FindAllStringSubmatchIndex(b_v, -1)
+		for _, v := range array {
+			var wrfg *WinnerFlag
+			if isWinnerReg1 {
+				wrfg = &WinnerFlag{
+					index:     wo.toNumber(b_v[v[4]:v[5]], 0),
+					textStart: v[4],
+					textEnd:   v[5],
+				}
+			} else {
+				wrfg = &WinnerFlag{
+					index:     wo.toNumber(b_v[v[2]:v[3]], 0),
+					textStart: v[2],
+					textEnd:   v[3],
+				}
+			}
+			indexs = append(indexs, wrfg)
+		}
+		wf := wo.getMax(indexs, from)
+		if wf != nil && wf.max >= prevMax {
+			prevMax = wf.max
+			rightIndex = b_k
+			winnerFlag = wf
+		}
+	}
+	//在这个块里面,截取
+	if rightIndex == -1 {
+		return ""
+	}
+	text = blocks[rightIndex]
+	warpCount := wo.interceptText(winnerFlag.indexs, text)
+	if warpCount == 0 {
+		warpCount = 1
+	}
+	textEnd := text[winnerFlag.textEnd:]
+	text = text[winnerFlag.textStart:winnerFlag.textEnd]
+	warpIndex := regSpliteSegment.FindAllStringIndex(textEnd, -1)
+	if len(warpIndex) >= warpCount {
+		textEnd = textEnd[:warpIndex[warpCount-1][1]]
+	}
+	text = text + textEnd
+	if isWinnerReg1 {
+		text = reg_2.ReplaceAllString(text, "$1\n$2$15")
+	} else {
+		text = reg_2.ReplaceAllString(text, "\n$1")
+	}
+	text = regReplWrapSpace.ReplaceAllString(text, "")
+	lines := spacekvEntity.getLines(text)
+	text = ""
+	for k, v := range lines {
+		v = strings.TrimSpace(v)
+		v = colonSpaceReg.ReplaceAllString(v, ":")
+		if reg_2.MatchString(v) && !regDivision.MatchString(v) {
+			if isWinnerReg1 {
+				v = reg_2.ReplaceAllString(v, "$1$2:$15")
+			} else {
+				v = reg_2.ReplaceAllString(v, "$1:")
+			}
+		}
+		//逗号之类符号的分割,查找紧跟在中标候选人之后的中标金额
+		//如果后面没有什么标识,只有金额的情况下,把中标金额加到金额前面
+		if reg_2.MatchString(v) {
+			//两个kv连到一起
+			if len(regDivision.FindAllString(v, -1)) > 1 && !findamountReg.MatchString(v) {
+				v = companyWarpReg.ReplaceAllString(v, "$1\n$2")
+			}
+			vs := findamountReg.Split(v, -1)
+			if len(vs) > 1 {
+				vs_1 := strings.TrimSpace(vs[1])
+				if amountReg.MatchString(vs_1) {
+					v = strings.Replace(v, vs[1], "中标金额:"+vs_1, 1)
+				}
+			}
+		}
+		v = toWarpReg.ReplaceAllString(v, "\n")
+		text += v
+		if (!reg_2.MatchString(v) || !colonEndReg.MatchString(v)) && k < len(lines)-1 {
+			text += "\n"
+		}
+	}
+	return text
+}
+
+//抽取对应的排序结果
+func (wo *WinnerOrderEntity) findByReg(content string, blocks []string, reg_2 *regexp.Regexp, from int) []map[string]interface{} {
+	text := wo.getText(content, blocks, reg_2, from)
+	winners := []map[string]interface{}{}
+	object := map[string]interface{}{}
+	count := 0
+	kvs := colonkvEntity.getColonSpaceKV(text)
+	for _, kv := range kvs {
+		k, v := kv.Key, kv.Value
+		if regDivision.MatchString(v) {
+			v_k := regDivision.Split(v, -1)[0]
+			if reg_2.MatchString(v_k) {
+				k = v_k
+			}
+		}
+		if reg_2.MatchString(k) { //中标人
+			if len(object) > 0 {
+				winners = append(winners, object)
+				object = map[string]interface{}{}
+			}
+			val := wo.clear("中标单位", v)
+			if val != nil {
+				count++
+				object["entname"] = val
+				object["sort"] = wo.toNumber(k, count)
+				object["sortstr"] = thisNumberReg.FindString(k)
+				object["type"] = 1
+			}
+		} else { //中标金额
+			_, standardKvTag := KvTagsToKV([]*util.Kv{&util.Kv{Key: k, Value: v}}, "", []string{"中标金额"}, 1)
+			//找到了中标金额
+			if standardKvTag["中标金额"] != nil && object["entname"] != nil {
+				val := wo.clear("中标金额", standardKvTag["中标金额"].Value)
+				if val != nil {
+					object["price"] = val
+				}
+				winners = append(winners, object)
+				object = map[string]interface{}{}
+			}
+		}
+	}
+	if len(object) > 0 {
+		winners = append(winners, object)
+	}
+	indexs := []*WinnerFlag{}
+	for _, winner := range winners {
+		indexs = append(indexs, &WinnerFlag{
+			index: winner["sort"].(int),
+		})
+	}
+	winnerFlag := wo.getMax(indexs, from)
+	if winnerFlag != nil {
+		winners = winners[winnerFlag.start : winnerFlag.end+1]
+	} else {
+		winners = []map[string]interface{}{}
+	}
+	return winners
+}
+
+//清理结果
+func (wo *WinnerOrderEntity) clear(typ, v string) interface{} {
+	if typ == "中标单位" && regDivision.MatchString(v) {
+		v = findCompanyReg.FindString(v)
+	}
+	return clear.ClearResult(typ, v)
+}
+
+//
+func (wo *WinnerOrderEntity) toNumber(value string, defaultNum int) int {
+	value = numberReg.FindString(value)
+	if value == "" {
+		return defaultNum
+	}
+	v := util.ChineseNumberToInt(value)
+	if v < 1 {
+		v, _ = strconv.Atoi(value)
+	}
+	if v > 0 {
+		return v
+	}
+	return defaultNum
+}
+
+//
+func (wo *WinnerOrderEntity) getMax(indexs []*WinnerFlag, from int) *WinnerFlag {
+	allMap := map[int]*WinnerFlag{}
+	max, start, textStart := -1, -1, -1
+	isContinue := false
+	flag := false
+	is := []int{}
+	for k, winnerFlag := range indexs {
+		v := winnerFlag.index
+		//从1开始,1前面的过滤掉
+		if v == 1 {
+			flag = true
+		}
+		if !flag {
+			continue
+		}
+		if v == 1 {
+			start = k
+			textStart = winnerFlag.textStart
+			isContinue = false
+		}
+		if isContinue {
+			continue
+		}
+		nextIndex := 0
+		if k < len(indexs)-1 {
+			nextIndex = indexs[k+1].index
+		}
+		//从1-n是一组,遇到小于n的从新添加分组,分组不是1开头的过滤掉
+		is = append(is, winnerFlag.textStart, winnerFlag.textEnd)
+		if nextIndex-v != 1 {
+			isContinue = true
+			if max < k-start {
+				max = k - start
+				allMap[k-start] = &WinnerFlag{
+					textStart: textStart,
+					textEnd:   winnerFlag.textEnd,
+					start:     start,
+					end:       k,
+					max:       max,
+					indexs:    is,
+				}
+			}
+		}
+	}
+	if max != -1 {
+		if from != 3 && len(allMap[max].indexs) <= 2 {
+			return nil
+		}
+		return allMap[max]
+	}
+	return nil
+}
+
+//如果有两个,看第一个有几个换行,用第一个里面的最后一个换行作为第二个的结束位置
+//如果有两个以上,取前两个中换行最多的最后一个换行,作为其他的结束位置
+func (wo *WinnerOrderEntity) interceptText(indexs []int, con string) int {
+	if len(indexs) == 1 {
+		return 0
+	}
+	count := 0
+	for ik, iv := range indexs {
+		text := ""
+		if ik < len(indexs)-1 {
+			text = con[iv:indexs[ik+1]]
+		} else {
+			text = con[iv:]
+		}
+		//如果两个
+		if len(indexs) == 2 {
+			//取第一个有几个换行符
+			if ik == 0 {
+				count = len(regSpliteSegment.FindAllStringIndex(text, -1))
+			}
+		} else {
+			//多个,取前两个中换行符最多的
+			if ik <= 1 {
+				thisCount := len(regSpliteSegment.FindAllStringIndex(text, -1))
+				if thisCount > count {
+					count = thisCount
+				}
+			}
+		}
+	}
+	return count
+}
+
+//排序
+func (wo *WinnerOrderEntity) Order(winnerOrder []map[string]interface{}) {
+	if winnerOrder == nil || len(winnerOrder) <= 1 {
+		return
+	}
+	for x, _ := range winnerOrder {
+		for y := 0; y < len(winnerOrder)-x-1; y++ {
+			dt1, xok := winnerOrder[y]["sort"].(int)
+			dt2, yok := winnerOrder[y+1]["sort"].(int)
+			if xok && yok && dt1 > dt2 {
+				temp := winnerOrder[y]
+				winnerOrder[y] = winnerOrder[y+1]
+				winnerOrder[y+1] = temp
+			}
+		}
+	}
+}
+
+//合并
+func (wo *WinnerOrderEntity) Merge(winnerOrder, wors []map[string]interface{}) {
+	if wors == nil || len(wors) == 0 {
+		return
+	}
+	for _, v := range wors {
+		for _, tv := range winnerOrder {
+			sort, _ := v["sort"].(int)
+			t_sort, _ := tv["sort"].(int)
+			if sort == 0 || sort != t_sort {
+				continue
+			}
+			if qutil.ObjToString(tv["entname"]) == "" && qutil.ObjToString(v["entname"]) != "" {
+				tv["entname"] = v["entname"]
+			}
+			t_price, _ := tv["price"].(float64)
+			price, _ := v["price"].(float64)
+			if t_price == 0 && price != 0 {
+				tv["price"] = v["price"]
+			}
+		}
+	}
+}

+ 87 - 0
src/jy/util/article.go

@@ -0,0 +1,87 @@
+package util
+
+//
+type Job struct {
+	SourceMid  string                  //数据源的MongoId
+	Category   string                  //类别
+	Content    string                  //正文
+	Title      string                  //标题
+	SpiderCode string                  //爬虫代码
+	Domain     string                  //网站域名
+	Href       string                  //原文链接
+	City       string                  //城市
+	Province   string                  //省份
+	Data       *map[string]interface{} //数据库源数据
+	Block      []*Block                //分块
+	Result     map[string][]*ExtField  //结果
+}
+
+type ExtField struct {
+	Field string      //属性
+	Key   string      //匹配标签或正则
+	Type  string      //抽取类型
+	Value interface{} //抽取结果
+}
+
+//块
+type Block struct {
+	Tags     []Tags          //对块做的标签,可以作为数据抽取的依据
+	Title    string          //块标题
+	Index    int             //块索引
+	Text     string          //块内容
+	Start    int             //开始索引
+	End      int             //结束索引
+	ColonKV  *JobKv          //冒号kv (分出的对应的KV值)
+	TableKV  *JobKv          //table kv (分出的对应的KV值)
+	SpaceKV  *JobKv          //空格 kv (分出的对应的KV值)
+	BPackage *BlockPackage   //分包信息
+	Tag      map[string]bool //块标签
+	Block    []*Block        //子块
+}
+
+//段落
+type Segment struct {
+	Index int    //段落索引
+	Text  string //段落内容
+}
+
+//包
+type BlockPackage struct {
+	Index       string                   //序号 (转换后编号,只有数字或字母)
+	Origin      string                   //包的原始值
+	Type        string                   //类型 (匹配后面的标段、包之类的词)
+	Text        string                   //包文 (包对应的正文)
+	ColonKV     *JobKv                   //冒号kv (分出的对应的KV值)
+	TableKV     *JobKv                   //table kv (分出的对应的KV值)
+	SpaceKV     *JobKv                   //空格 kv (分出的对应的KV值)
+	BidStatus   string                   //成交状态
+	WinnerOrder []map[string]interface{} //中标人排序
+	Accuracy    bool                     //包里面抽取字段的准确性,如果能打上块标签的话,就不用中标候选人中的值覆盖包里面的值
+}
+
+//kv
+type Kv struct {
+	Key      string
+	Value    string
+	Line     string
+	PrevLine string
+	NextLine string
+	Title    string
+}
+
+//最终放到job上的kv
+type JobKv struct {
+	Kvs   []*Kv             //有序的冒号kv
+	Kvs_2 []*Kv             //有序的冒号kv
+	Kv    map[string]string //table kv (分出的对应的KV值)
+	KvTag map[string]*Tag   //带权重的kv
+}
+
+func NewJobKv() *JobKv {
+	return &JobKv{
+		Kvs:   []*Kv{},
+		Kvs_2: []*Kv{},
+		Kv:    map[string]string{},
+		KvTag: map[string]*Tag{},
+	}
+}

+ 104 - 0
src/jy/util/config.go

@@ -0,0 +1,104 @@
+//
+package util
+
+import (
+	"log"
+	"os"
+	"qfw/util"
+	"regexp"
+	"sync"
+
+	"github.com/dlclark/regexp2"
+)
+
+const (
+	EXTRACT_Original = iota
+	EXTRACT_Fun
+	EXTRACT_ColonKV
+	EXTRACT_SpaceKV
+	EXTRACT_TableKV
+	EXTRACT_TitleReg
+	EXTRACT_BlockReg
+	EXTRACT_TextReg
+	EXTRACT_PagReg
+)
+
+//全局配置
+var FormatTextMap map[string][]map[string]interface{}
+var StandardNameMap = map[string]*NodeConfig{}
+
+/*单个字段抽取环节配置
+正常抽取,只用写配置正则就可以了
+对于复杂的抽取,支持用GO写方法,平台直接调用方法
+*/
+type NodeConfig struct {
+	Code          string   `json:"code"`
+	Field         string   `json:"field"`
+	Descript      string   `json:"descript"`
+	DomainMatch   string   `json:"domainmatch"`
+	PreRegStr     []string `json:"prereg"`
+	PreReg        []map[*regexp.Regexp]string
+	RegStr        []string `json:"reg"` //抽取用得正则
+	Reg           []*ExtractReg
+	PackageRegStr []string `json:"packagereg"` //分包抽取用得正则
+	PackageReg    []*ExtractReg
+	TitleRegStr   []string `json:"titlereg"` //抽取用得正则
+	TitleReg      []*ExtractReg
+	EndRegStr     string `json:"endreg"`
+	EndReg        *regexp.Regexp
+	Fn            string   `json:"fn"`    //
+	Clear         []string `json:"clear"` //抽取后用于清理的方法链
+	//其他配置项,或者限制条件
+	NeedSave            bool                         `json:"needsave"` // 是否保存
+	DefaultVal          map[string]interface{}       `json:"defaultval"`
+	EndFilter           []string                     `json:"endfilter"`
+	EndFilterArray      []map[*regexp.Regexp]string  //结尾过滤正则
+	KvEndFilter         []string                     `json:"kvendfilter"`
+	KvEndFilterArray    []map[*regexp2.Regexp]string //kv清理过滤正则
+	TitleEndFilter      []string                     `json:"titlendfilter"`
+	TitleEndFilterArray []map[*regexp2.Regexp]string //kv清理过滤正则
+	CheckNum            bool                         `json:"checknum"`
+}
+
+type ExtractReg struct {
+	Reg        *regexp.Regexp
+	Bextract   bool
+	ExtractPos int
+}
+
+/**
+  分类配置
+*/
+
+var NodeConfigs map[string] /*分类代码*/ map[string] /*domain代码*/ *NodeConfig //字段抽取配置
+var Schemas map[string][]string                                           //
+var lock sync.Mutex
+
+func init() {
+	loadFormatText()
+}
+
+//加载格式化正文配置
+func loadFormatText() {
+	FormatTextMap = map[string][]map[string]interface{}{}
+	var formatTextMap map[string]interface{}
+	util.ReadConfig("./res/formattext.json", &formatTextMap)
+	if len(formatTextMap) == 0 {
+		log.Println("formattext.json配置文件中有错误!")
+		os.Exit(0)
+	}
+	for k, v := range formatTextMap {
+		array := v.([]interface{})
+		arrayMaps := []map[string]interface{}{}
+		for _, ai := range array {
+			a, _ := ai.(map[string]interface{})
+			reg, _ := a["reg"].(string)
+			if reg == "" {
+				continue
+			}
+			a["reg"] = regexp.MustCompile(reg)
+			arrayMaps = append(arrayMaps, a)
+		}
+		FormatTextMap[k] = arrayMaps
+	}
+}

+ 72 - 11
src/jy/util/script.go

@@ -4,6 +4,7 @@ package util
 import (
 	"encoding/json"
 	"fmt"
+	qu "qfw/util"
 
 	ljson "github.com/yuin/gopher-json"
 	"github.com/yuin/gopher-lua"
@@ -11,11 +12,15 @@ import (
 
 type LuaScript struct {
 	Code, Name, Script string
-	Doc                map[string]interface{}
+	Doc                map[string]interface{} //经过前置过滤的源信息
+	Block              []*Block               //分块信息
+	KvMap              map[string][]map[string]interface{}
+	Result             map[string][]map[string]interface{} //抽取结果信息
 	L                  *lua.LState
 }
 
-func (s *LuaScript) RunScript() map[string]interface{} {
+//stype per:前置,core:抽取,back:后置
+func (s *LuaScript) RunScript(stype string) map[string]interface{} {
 	data := map[string]interface{}{}
 	s.L = lua.NewState()
 	s.L.PreloadModule("json", ljson.Loader)
@@ -23,13 +28,35 @@ func (s *LuaScript) RunScript() map[string]interface{} {
 	if err := s.L.DoString(s.Script); err != nil {
 		data["err"] = err.Error()
 	} else {
-		tab := MapToLuaTable(s.L, s.Doc)
-		if err := s.L.CallByParam(lua.P{
-			Fn:      s.L.GetGlobal("main"),
-			NRet:    1,
-			Protect: true,
-		}, tab); err != nil {
-			data["err"] = err.Error()
+		if stype == "pre" {
+			tab := MapToLuaTable(s.L, s.Doc)
+			if err := s.L.CallByParam(lua.P{
+				Fn:      s.L.GetGlobal("main"),
+				NRet:    1,
+				Protect: true,
+			}, tab); err != nil {
+				data["err"] = err.Error()
+			}
+		} else if stype == "core" {
+			tab := MapToLuaTable(s.L, s.Doc)
+			block, _ := json.Marshal(s.Block)
+			kvMap := MapToLuaTable2(s.L, s.KvMap)
+			if err := s.L.CallByParam(lua.P{
+				Fn:      s.L.GetGlobal("main"),
+				NRet:    1,
+				Protect: true,
+			}, tab, lua.LString(block), kvMap); err != nil {
+				data["err"] = err.Error()
+			}
+		} else if stype == "back" {
+			result := MapToLuaTable2(s.L, s.Result)
+			if err := s.L.CallByParam(lua.P{
+				Fn:      s.L.GetGlobal("main"),
+				NRet:    1,
+				Protect: true,
+			}, result); err != nil {
+				data["err"] = err.Error()
+			}
 		}
 		ret := s.L.Get(-1)
 		s.L.Pop(1)
@@ -44,7 +71,6 @@ func (s *LuaScript) RunScript() map[string]interface{} {
 func Logic(str string, doc map[string]interface{}) bool {
 	L := lua.NewState()
 	L.PreloadModule("json", ljson.Loader)
-
 	defer L.Close()
 	b := false
 	if err := L.DoString(str); err != nil {
@@ -84,6 +110,18 @@ func MapToLuaTable(l *lua.LState, obj map[string]interface{}) *lua.LTable {
 			tab.RawSet(lua.LString(k), lua.LBool(val))
 		} else if val, ok := v.(map[string]interface{}); ok {
 			tab.RawSet(lua.LString(k), MapToLuaTable(l, val))
+		} else if val, ok := v.([]string); ok {
+			tb := l.NewTable()
+			for k, vv := range val {
+				tb.Insert(k, lua.LString(vv))
+			}
+			tab.RawSet(lua.LString(k), tb)
+		} else if val, ok := v.([]map[string]interface{}); ok {
+			tab2 := l.NewTable()
+			for i, v := range val {
+				tab2.Insert(i, MapToLuaTable(l, v))
+			}
+			tab.RawSet(lua.LString(k), tab2)
 		} else if val, ok := v.([]interface{}); ok {
 			bs, _ := json.Marshal(val)
 			tab.RawSet(lua.LString(k), lua.LString(string(bs)))
@@ -92,6 +130,18 @@ func MapToLuaTable(l *lua.LState, obj map[string]interface{}) *lua.LTable {
 	return tab
 }
 
+func MapToLuaTable2(l *lua.LState, obj map[string][]map[string]interface{}) *lua.LTable {
+	tab := l.NewTable()
+	for k, ms := range obj {
+		tab2 := l.NewTable()
+		for i, v := range ms {
+			tab2.Insert(i, MapToLuaTable(l, v))
+		}
+		tab.RawSet(lua.LString(k), tab2)
+	}
+	return tab
+}
+
 func LuaTableToMap(param *lua.LTable) map[string]interface{} {
 	tmp := map[string]interface{}{}
 	param.ForEach(func(key, val lua.LValue) {
@@ -99,7 +149,18 @@ func LuaTableToMap(param *lua.LTable) map[string]interface{} {
 		if v, ok := val.(lua.LString); ok {
 			tmp[k] = string(v)
 		} else if v, ok := val.(*lua.LTable); ok {
-			tmp[k] = LuaTableToMap(v)
+			i := qu.IntAllDef(k, -1)
+			if i > -1 { //转数组
+				t := []map[string]interface{}{}
+				v.ForEach(func(k, inv lua.LValue) {
+					if vv, ok := inv.(*lua.LTable); ok {
+						t = append(t, LuaTableToMap(vv))
+					}
+				})
+				tmp[k] = t
+			} else {
+				tmp[k] = LuaTableToMap(v)
+			}
 		} else if v, ok := val.(*lua.LBool); ok {
 			if v.String() == "true" {
 				tmp[k] = true

+ 20 - 0
src/jy/util/tablek.go

@@ -0,0 +1,20 @@
+package util
+
+import (
+	"qfw/util"
+)
+
+//初始化解析table表格用到的表头字典库
+
+type TableK struct {
+	Key []string
+}
+
+var TableK1 = map[string][]string{}
+
+var TableKey *TableK
+
+func init() {
+	util.ReadConfig("./res/tablek.json", &TableKey)
+	util.ReadConfig("./res/tablev1.json", &TableK1)
+}

+ 148 - 0
src/jy/util/tagmatch.go

@@ -0,0 +1,148 @@
+package util
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+)
+
+//单条tag
+type Tag struct {
+	Value  string //
+	Weight int    //权重
+}
+type Tags []*Tag //
+//tag文件
+type TagFile struct {
+	Name  string //
+	Items Tags   //数据
+}
+
+//实现排序接口
+func (s Tags) Len() int {
+	return len(s)
+}
+func (s Tags) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+func (s Tags) Less(i, j int) bool {
+	return strings.Compare(s[i].Value, s[j].Value) < 1
+}
+
+//
+var tagdb = make(map[string]*TagFile)
+var blocktagdb = make(map[string]*TagFile)
+
+//加载
+func (t *TagFile) Load(path string) {
+	fi, _ := os.Open(path)
+	info, _ := fi.Stat()
+	name := info.Name()
+	bs, _ := ioutil.ReadAll(fi)
+	fi.Close()
+	lines := strings.Split(strings.Replace(string(bs), "\r", "", -1), "\n")
+	name = name[:strings.Index(name, ".")]
+	//排序
+	t.Items = make([]*Tag, len(lines))
+	for k, v := range lines {
+		t.Items[k] = &Tag{v, 0 - k}
+	}
+	sort.Sort(t.Items)
+	t.Name = name
+}
+
+//快速匹配
+func (t *TagFile) Match(src string) (bool, *Tag) {
+	pos := binarysearch(t.Items, 0, len(t.Items)-1, src)
+	if pos < 0 {
+		return false, nil
+	} else {
+		return true, t.Items[pos]
+	}
+
+}
+
+//2分查找,返回位置
+func binarysearch(arr Tags, begin, end int, key string) int {
+	if begin > end {
+		return -1
+	}
+	mid := (begin + end) / 2
+	if mid < 0 {
+		return -1
+	}
+	if arr[mid].Value == key {
+		return mid
+	} else if arr[mid].Value > key {
+		return binarysearch(arr, begin, mid-1, key)
+	} else {
+		return binarysearch(arr, mid+1, end, key)
+	}
+}
+
+//加载指定目录下的所有文件
+func LoadTagDb(respath string) {
+	filepath.Walk(respath, func(path string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return nil
+		}
+		if strings.HasSuffix(info.Name(), ".txt") {
+			t := &TagFile{}
+			t.Load(path)
+			if strings.HasSuffix(respath, "/tagdb") {
+				tagdb[t.Name] = t
+			} else if strings.HasSuffix(respath, "/blocktagdb") {
+				blocktagdb[t.Name] = t
+			}
+		}
+		return nil
+	})
+}
+
+//取得匹配
+func GetTags(src string) Tags {
+	return GetAppointTags(src, nil)
+}
+
+//根据指定的标签库取得匹配
+func GetAppointTags(src string, array []string) Tags {
+	src = TrimLRAll(src, "")
+	ret := make(Tags, 0)
+	m := map[string]bool{}
+	if array != nil && len(array) > 0 {
+		for _, v := range array {
+			m[v] = true
+		}
+	}
+	for k, v := range tagdb {
+		if len(m) > 0 && !m[k] {
+			continue
+		}
+		if ok, tag := v.Match(src); ok {
+			ret = append(ret, &Tag{
+				v.Name,
+				tag.Weight,
+			})
+		}
+	}
+	//sort.Sort(ret)
+	return ret
+}
+
+//取得匹配
+func GetBlockTags(src string) Tags {
+	src = TrimLRAll(src, "")
+	ret := make(Tags, 0)
+	for _, v := range blocktagdb {
+		if ok, tag := v.Match(src); ok {
+			ret = append(ret, &Tag{
+				v.Name,
+				tag.Weight,
+			})
+		}
+	}
+	//sort.Sort(ret)
+	return ret
+}

+ 3 - 12
src/jy/util/util.go

@@ -55,26 +55,17 @@ func GetSyncIndex(code string) string {
 }
 
 //nfields非复制字段集
-func DeepCopy(value interface{}, nfields []string) interface{} {
+func DeepCopy(value interface{}) interface{} {
 	if valueMap, ok := value.(map[string]interface{}); ok {
 		newMap := make(map[string]interface{})
 		for k, v := range valueMap {
-			iscop := true
-			for _, fn := range nfields {
-				if k == fn {
-					iscop = false
-					break
-				}
-			}
-			if iscop {
-				newMap[k] = DeepCopy(v, []string{})
-			}
+			newMap[k] = DeepCopy(v)
 		}
 		return newMap
 	} else if valueSlice, ok := value.([]interface{}); ok {
 		newSlice := make([]interface{}, len(valueSlice))
 		for k, v := range valueSlice {
-			newSlice[k] = DeepCopy(v, []string{})
+			newSlice[k] = DeepCopy(v)
 		}
 		return newSlice
 	}

+ 261 - 0
src/jy/util/util2.go

@@ -0,0 +1,261 @@
+package util
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+const (
+	conStr = "([\\s\u3000\u2003\u00a0]+|\\\\t)" //所有空格
+)
+
+var (
+	LReg            = regexp.MustCompile("^" + conStr)
+	RReg            = regexp.MustCompile(conStr + "$")
+	LRReg           = regexp.MustCompile("^([\\s\u3000\u2003\u00a0]+|\\\\t)|([\\s\u3000\u2003\u00a0]+|\\\\t)$")
+	ALLReg          = regexp.MustCompile(conStr)
+	LRMReg          = regexp.MustCompile("^([::]+)|([::]+)$")
+	NoMatchMap      = map[string]bool{} //没有识别出来的标签
+	NoMatchMapCount = map[string]int{}  //没有识别出来的标签
+	NoMatchMapLock  = &sync.Mutex{}
+	PhoneReg        = regexp.MustCompile("((([((]\\d{3,4}[))])?(\\d{6,12}([×―—-\\-]+\\d{3,4})?|\\d{3,4}[×―—-\\-]+[\u3000\u2003\u00a0\\s]*\\d{6,12}([×―—-\\-]+\\d{4})?|(\\d{2}[×―—-\\-])+\\d{8}[×―—-\\-](\\d{3}[、])+)(转\\d{3,4})?[或/、,,;;\u3000\u2003\u00a0\\s]*)+(\\d{3,})?)")
+	ConvertIntReg_1 = regexp.MustCompile("^[0-9]+[一二三四五六七八九十]+$")
+	ConvertIntReg_2 = regexp.MustCompile("^[a-zA-Z0-9\\-]+$")
+	ConvertIntReg_3 = regexp.MustCompile("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+$")
+	ConvertIntReg_4 = regexp.MustCompile("^[①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳]$")
+	ConvertIntReg_5 = regexp.MustCompile("^[一二三四五六七八九十]+$")
+	ConvertIntReg_6 = regexp.MustCompile("[0-9]+")
+	ConvertIntReg_7 = regexp.MustCompile("^" + ConvertIntReg_6.String() + "$")
+	//标段号,包号转换
+	FindPkgNumReg   = regexp.MustCompile("(\\d[.])+\\d|([一二三四五六七八九十0-9A-Za-zⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳\\-]+)|(设计|施工|监理|验收)")
+	FindPkgNumReg_1 = regexp.MustCompile("设计|施工|监理|验收")
+)
+
+func AddtoNoMatchMap(key string) {
+	NoMatchMapLock.Lock()
+	defer NoMatchMapLock.Unlock()
+	NoMatchMapCount[key]++
+}
+
+func TrimLeftSpace(con, repl string) string {
+	return LReg.ReplaceAllString(con, repl)
+}
+
+func TrimRightSpace(con, repl string) string {
+	return RReg.ReplaceAllString(con, repl)
+}
+
+func TrimLRSpace(con, repl string) string {
+	return LRReg.ReplaceAllString(con, repl)
+}
+func TrimLRAll(con, repl string) string {
+	return LRMReg.ReplaceAllString(LRReg.ReplaceAllString(con, repl), repl)
+}
+
+// get 网络请求
+func Get(apiURL string, params url.Values) (rs []byte, err error) {
+	var Url *url.URL
+	Url, err = url.Parse(apiURL)
+	if err != nil {
+		fmt.Printf("解析url错误:\r\n%v", err)
+		return nil, err
+	}
+	//如果参数中有中文参数,这个方法会进行URLEncode
+	Url.RawQuery = params.Encode()
+	resp, err := http.Get(Url.String())
+	if err != nil {
+		fmt.Println("err:", err)
+		return nil, err
+	}
+	defer resp.Body.Close()
+	return ioutil.ReadAll(resp.Body)
+}
+
+// post 网络请求 ,params 是url.Values类型
+func Post(apiURL string, params url.Values) (rs []byte, err error) {
+	resp, err := http.PostForm(apiURL, params)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	return ioutil.ReadAll(resp.Body)
+}
+
+//中文数字转换
+func ChineseNumberToInt(value string) int {
+	result, temp, count := 0, 1, 0
+	cnArr := []string{"一", "二", "三", "四", "五", "六", "七", "八", "九"}
+	chArr := []string{"十", "百", "千", "万", "亿"}
+	for i, v := range []rune(value) {
+		b := true //判断是否是chArr
+		c := string(v)
+		for j, cv := range cnArr { //非单位,即数字
+			if c == cv {
+				if 0 != count { //添加下一个单位之前,先把上一个单位值添加到结果中
+					result += temp
+					temp = 1
+					count = 0
+				}
+				// 下标+1,就是对应的值
+				temp = j + 1
+				b = false
+				break
+			}
+		}
+		if b { //单位{'十','百','千','万','亿'}
+			for j, cv := range chArr {
+				if c == cv {
+					b = false
+					switch j {
+					case 0:
+						temp *= 10
+						break
+					case 1:
+						temp *= 100
+						break
+					case 2:
+						temp *= 1000
+						break
+					case 3:
+						temp *= 10000
+						break
+					case 4:
+						temp *= 100000000
+						break
+					default:
+						break
+					}
+					count++
+				}
+			}
+		}
+		if b {
+			return -1
+		}
+		if i == len([]rune(value))-1 { //遍历到最后一个字符
+			result += temp
+		}
+	}
+	return result
+}
+
+//罗马数字转换
+func romeNumberToInt(value string) int {
+	for k, v := range []rune("ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ") {
+		if value == string(v) {
+			return k + 1
+		}
+	}
+	return -1
+}
+
+//带圆圈的数字换行
+func circleNumberToInt(value string) int {
+	for k, v := range []rune("①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳") {
+		if value == string(v) {
+			return k + 1
+		}
+	}
+	return -1
+}
+
+func PackageNumberConvert(val string) string {
+	result := val
+	for _, value := range strings.Split(val, "/") {
+		result = value
+		value = ALLReg.ReplaceAllString(value, "")
+		if ConvertIntReg_7.MatchString(value) {
+			value = strings.TrimLeft(value, "0")
+		}
+		value = FindPkgNumReg.FindString(value)
+		if ConvertIntReg_1.MatchString(value) {
+			result = ConvertIntReg_6.FindString(value)
+		} else if ConvertIntReg_5.MatchString(value) {
+			result = value
+			if v := ChineseNumberToInt(value); v != -1 {
+				result = strconv.Itoa(v)
+				break
+			}
+		} else if ConvertIntReg_2.MatchString(value) && !strings.HasSuffix(value, "-") {
+			result = strings.ToUpper(value)
+		} else if ConvertIntReg_3.MatchString(value) {
+			if v := romeNumberToInt(value); v != -1 {
+				result = strconv.Itoa(v)
+				break
+			}
+		} else if ConvertIntReg_4.MatchString(value) {
+			if v := circleNumberToInt(value); v != -1 {
+				result = strconv.Itoa(v)
+				break
+			}
+		} else if FindPkgNumReg_1.MatchString(value) {
+			result = FindPkgNumReg_1.FindString(value)
+		}
+	}
+	result = strings.Replace(result, ".", "_", -1)
+	return result
+}
+
+//map中是否存在value
+func IsMapHasValue(value interface{}, m interface{}) bool {
+	if intStringMap, ok := m.(map[int]string); ok {
+		for _, v := range intStringMap {
+			if v == value {
+				return true
+			}
+		}
+	} else if stringRegMap, ok := m.(map[string]*regexp.Regexp); ok {
+		vs, _ := value.(string)
+		for _, v := range stringRegMap {
+			if v.MatchString(vs) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+//数组中是否存在value
+func IsArrayHasValue(value interface{}, m interface{}) bool {
+	if stringArray, ok := m.([]string); ok {
+		for _, v := range stringArray {
+			if v == value {
+				return true
+			}
+		}
+	} else if intArray, ok := m.([]int); ok {
+		for _, v := range intArray {
+			if v == value {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+//获取map中第一个key的值
+func FirstKeyValueInMap(m interface{}) (string, interface{}) {
+	if mv, ok := m.(map[string]*Tag); ok {
+		for k, v := range mv {
+			return k, v.Weight
+		}
+	} else if mv, ok := m.(map[string]string); ok {
+		for k, v := range mv {
+			return k, v
+		}
+	} else if mv, ok := m.(map[int]string); ok {
+		for k, v := range mv {
+			return strconv.Itoa(k), v
+		}
+	}
+	return "", nil
+}
+func Debug(v ...interface{}) {
+
+}

+ 4 - 5
src/jy/extract/extract_test.go → src/main_test.go

@@ -1,15 +1,14 @@
-// extract_test
-package extract
+package main
 
 import (
+	"jy/extract"
 	. "jy/mongodbutil"
-
 	"testing"
 	"time"
 )
 
 func Test_task(t *testing.T) {
 	Mgo = MgoFactory(1, 3, 120, "192.168.3.207:27082", "rztest")
-	StartExtractTaskId("5b8f804025e29a290415aee1")
-	time.Sleep(2 * time.Minute)
+	extract.StartExtractTaskId("5b8f804025e29a290415aee1")
+	time.Sleep(15 * time.Second)
 }

+ 187 - 0
src/res/formattext.json

@@ -0,0 +1,187 @@
+{
+    "all": [
+		{
+            "reg": "(项目名称)及(编号)[::](.+?),(.+)",
+            "separator": "$1:$3\n项目$2:$4",
+            "desc": ""
+        },
+        {
+            "reg": "([^项目概况|\u4e00-\u9fa5]{2,10})以?及([^招标内容|\u4e00-\u9fa5]{2,10})[::](.+?),(.+)",
+            "separator": "$1:$3\n$2:$4",
+            "desc": ""
+        },
+		{
+            "reg": "项目名称[::].+?([\r\n]编号[::为].+|[((]编号[::].+?[))])",
+            "separator": "编号[::为]__项目编号:",
+            "desc": ""
+        },
+        {
+            "reg": "[((][大小]写.*?[))]",
+            "separator": " ",
+            "desc": "替换掉无效的kv"
+        },
+        {
+            "reg": "[^\\n::]{2,18}[::]\\s*详见[^,。,.::\\s]{2,18}",
+            "separator": "",
+            "desc": "替换掉无效的kv"
+        },
+        {
+            "reg": "(\\d+[,,.]+)+\\d+((百|千)?元|(百|千)?(万|亿)元?)",
+            "separator": "[,,]__",
+            "desc": "把金额里面的,号替换成,号 例如:8,88,8.8元 to 8888.8元"
+        },
+        {
+            "reg": "[^,,\\d](\\d{1,3}[,,]+)+\\d{3}",
+            "separator": "[,,]__",
+            "desc": "把金额里面的,号替换成,号 例如:8,88,8.8元 to 8888.8元"
+        },
+        {
+            "reg": "(.+?[((]((百|千)?元|(百|千)?(万|亿)元?)[))][::][\\d.]+)(.+?[::].+)",
+            "separator": "$1\n$6",
+            "desc": "两个kv连到一起的,指定k以换行符分隔开来"
+        },
+        {
+            "reg": "[((]¥[::][\\d,.]+[))]",
+            "separator": "¥[::]__",
+            "desc": "预中标价:壹佰柒拾捌万玖仟捌佰肆拾玖元整(¥:178984900)"
+        },
+        {
+            "reg": "(.{2,10}?[::][^、::,。\r\n]+?)(((采购)?代理机构|报价截止时间|成交供应商)[::].+)",
+            "separator": "$1\n$2",
+            "desc": "两个kv连到一起的,指定k以换行符分隔开来"
+        },
+        {
+            "reg": "[\r\n].{3,20}?[::]",
+            "separator": "([\u4e00-\u9fa5]+?)[\u3000\u2003\u00a0\\s]+__$1",
+            "desc": "例如:把采 购 人替换成采购人"
+        },
+        {
+            "reg": "([\u4e00-\u9fa5][^((,,。、.;;\r\n]{1,30}?[::][^\\s\u3000\u2003\u00a0,、。;;\r\n]+)([((])([\u4e00-\u9fa5][^,,。、.;;\r\n))包段]{1,30}?[::].+?)([))])",
+            "separator": "$1\n$2\n$3\n$4\n",
+            "desc": "例如:采购项目名称:脱贫攻坚大数据平台建设项目(项目编号:YLLBC20164002-HS)"
+        },
+        {
+            "reg_c": "([\u4e00-\u9fa5][^((,,。、.;;\r\n]{1,30}?[::][^\\s\u3000\u2003\u00a0,、。;;\r\n]+)([((])(.+?[::].+?)([))])",
+			"reg": "[((]([^::))\\r\\n]{2,10}[::][^::))\\r\\n]+)+[))]",
+            "separator": "[\\s\u3000\u2003\u00a0]__",
+            "desc": "项目名称:流通领域商品质量委托抽检(A 包:汽车配件及用品;B 包:家庭用品) "
+        },
+		{
+            "reg": "招标内容[::][((]共1包[))][\r\n]",
+            "separator": "[((]共1包[))][\r\n]__",
+            "desc": ""
+        },
+		{
+			"reg": "地[\\s\u3000\u2003\u00a0]+(址|点)|邮[\\s\u3000\u2003\u00a0]+政[\\s\u3000\u2003\u00a0]+编[\\s\u3000\u2003\u00a0]+码|邮[\\s\u3000\u2003\u00a0]+编|联[\\s\u3000\u2003\u00a0]+系[\\s\u3000\u2003\u00a0]+(人|方[\\s\u3000\u2003\u00a0]+式)|电[\\s\u3000\u2003\u00a0]+话|手[\\s\u3000\u2003\u00a0]+机|传[\\s\u3000\u2003\u00a0]+真|邮[\\s\u3000\u2003\u00a0]+箱|主[\\s\u3000\u2003\u00a0]+要[\\s\u3000\u2003\u00a0]+负[\\s\u3000\u2003\u00a0]+责[\\s\u3000\u2003\u00a0]+人",
+            "separator": "[\\s\u3000\u2003\u00a0]+__",
+            "desc": ""
+		},
+		{
+			"reg": "((地址|邮编)[::][^::\\s\u3000\u2003\u00a0]{5,})(联系人(姓名)?[::])",
+            "separator": "${1} ${3}",
+            "desc": ""
+		},
+		{
+			"reg": "(采购单位)(联系人)及(联系电话)[::](.+?)[::](.+)",
+            "separator": "$1$2:$4\n$1$3:$5",
+            "desc": ""
+		},
+		{
+			"reg": "([^((,,。、.;;::\r\n]{0,8})(联系人|地址)[::]([^\\s\u3000\u2003\u00a0,,]+?)(联系)?(电话(/传真)?|手机|传真|邮编)[::](.+)",
+            "separator": "\n${1}${2}:${3}\n${1}${5}:${7}",
+            "desc": ""
+		},
+		{
+			"reg": "([\n))])(联系人)及(手机|电话)[::](.+?)[\\s\u3000\u2003\u00a0/]+(.+)",
+            "separator": "$1$2:$4\n$3:$5",
+            "desc": ""
+		},
+		{
+			"reg": "\\n(.{2,8})(联系地址)、(联系人)及(电话)[::]([^\\s]+)\\s+([^\\s]+)\\s+(.+)",
+            "separator": "\n$1$2:$5\n$1$3:$6\n$1$4:$7",
+            "desc": ""
+		},
+		{
+			"reg": "\\n(.{2,8})联系方式[::](.+?)\\s+\\+\\s+(.+)",
+            "separator": "\n${1}联系人:$2\n${1}联系电话:$3",
+            "desc": ""
+		},
+		{
+			"reg": "(?s)([^((,,。、.;;::\\s\u3000\u2003\u00a0]{0,8})联系方式:联系人:",
+            "separator": "${1}联系人:",
+            "desc": ""
+		},
+		{
+			"reg": "\\n(.{2,8})(联系人)、(联系电话)[::]([\u4e00-\u9fa5、]+)(.+)",
+            "separator": "\n${1}${2}:${4}\n${1}${3}:${5}",
+            "desc": ""
+		},
+		{
+			"reg": "(收货)(联系人)和(联系方式)[::](.+?)/(.+)",
+            "separator": "${1}${2}:${4}\n${1}${3}:${5}",
+            "desc": ""
+		},
+		{
+			"reg": "(招标人[::][^::,、,]+?)(联系人[::].+)",
+            "separator": "${1}\n${2}",
+            "desc": ""
+		},
+		{
+			"reg": "(?s)([^((,,。、.;;::\\s\u3000\u2003\u00a0]{2,8})联系人[::]([\u4e00-\u9fa5、]+)\\s+((([((]\\d{3,4}[))])?(\\d{6,12}([×―—-\\-]+\\d{3,4})?|\\d{3,4}[×―—-\\-]+[\u3000\u2003\u00a0\\s]*\\d{6,12}([×―—-\\-]+\\d{4})?|(\\d{2}[×―—-\\-])+\\d{8}[×―—-\\-](\\d{3}[、])+)(转\\d{3,4})?[或/、,,;;\u3000\u2003\u00a0\\s]*)+(\\d{3,})?)",
+            "separator": "${1}联系人:${2}\n${1}联系电话:${3}",
+            "desc": ""
+		},
+		{
+			"reg": "(采购[^方式]{1,8})[::]([^::]{3,15})[,,]([^::]{2,5})((([((]\\d{3,4}[))])?(\\d{6,12}([×―—-\\-]+\\d{3,4})?|\\d{3,4}[×―—-\\-]+[\u3000\u2003\u00a0\\s]*\\d{6,12}([×―—-\\-]+\\d{4})?|(\\d{2}[×―—-\\-])+\\d{8}[×―—-\\-](\\d{3}[、])+)(转\\d{3,4})?[或/、,,;;\u3000\u2003\u00a0\\s]*)+(\\d{3,})?)",
+            "separator": "${1}:${2}\n${1}联系人:${3}\n${1}联系电话:${4}",
+            "desc": ""
+		},
+		{
+			"reg": "((联系|负责)人(姓名)?)和(电话)[::]([\u4e00-\u9fa5\\s]+)(.+)",
+            "separator": "${1}:${5}\n${4}:${6}",
+            "desc": ""
+		},
+		{
+			"reg": "(?s)([^((,,。、.;;::\\s\u3000\u2003\u00a0]{0,8})(联系.{1,4})(和|及)(.{2,4})[::][\u3000\u2003\u00a0\\s]*([\u4e00-\u9fa5]{2,5})[::\\s\u3000\u2003\u00a0]*((([((]\\d{3,4}[))])?(\\d{6,12}([×―—-\\-]+\\d{3,4})?|\\d{3,4}[×―—-\\-]+[\u3000\u2003\u00a0\\s]*\\d{6,12}([×―—-\\-]+\\d{4})?|(\\d{2}[×―—-\\-])+\\d{8}[×―—-\\-](\\d{3}[、])+)(转\\d{3,4})?[或/、,,;;\u3000\u2003\u00a0\\s]*)+(\\d{3,})?)",
+            "separator": "${1}${2}:${5}\n${1}${4}:${6}",
+            "desc": "采购人联系人和联系方式:雷蒙:13299985556 or 联系人及电话:  朱云鹏    13993240931"
+		},
+		{
+			"reg": "(?s)([^((,,。、.;;::\\s\u3000\u2003\u00a0]{0,8}?)(联系(方式|电话|人)和?)+[::]([^\\d::]{2,8}?)[((]?[\\s\u3000\u2003\u00a0]*((([((]\\d{3,4}[))])?(\\d{6,12}([×―—-\\-]+\\d{3,4})?|\\d{3,4}[×―—-\\-]+[\u3000\u2003\u00a0\\s]*\\d{6,12}([×―—-\\-]+\\d{4})?|(\\d{2}[×―—-\\-])+\\d{8}[×―—-\\-](\\d{3}[、])+)(转\\d{3,4})?[或/、,,;;\u3000\u2003\u00a0\\s]*)+(\\d{3,})?)",
+            "separator": "${1}联系人:${4}\n${1}联系电话:${5}",
+            "desc": "采购人联系方式:李静  0311-66629799 or 联系电话:张先生 0917―2660282"
+		},
+		{
+			"reg": "[((]([^))]{2,8}联系人)[::](.+?)[,,]((联系)?(电话|手机)(号码)?)[::](.+)[))]",
+            "separator": "\n${1}:${2}\n${3}:${7}",
+            "desc": ""
+		},
+		{
+			"reg": "\n(.{0,8})联系(人|方式)([::](.+?))[,,]((联系)?(电话|手机)(号码)?[::](.+))",
+            "separator": "\n${1}联系人${3}\n${5}",
+            "desc": ""
+		}
+    ],
+    "kv": [
+        {
+            "reg": "</?td[^>]*>",
+            "separator": "",
+            "desc": "把td清理掉"
+        },
+        {
+            "reg": "</?t[rh][^>]*>",
+            "separator": "\n",
+            "desc": "tr或th替换成换行"
+        },
+        {
+            "reg": "[\\s\u3000\u2003\u00a0]+[^\r\n]([一二三四五六七八九十]+[、..]|\\d+、)",
+            "separator": "\n$1",
+            "desc": "给没有换行的序号添加换行"
+        },
+        {
+            "reg": "\n[\\d.\u3000\u2003\u00a0\\s]*(联系人)及(电话)[::](.+?)[\u3000\u2003\u00a0\\s]+(.+)",
+            "separator": "\n$1:$3\n$2:$4",
+            "desc": ""
+        }
+    ]
+}

+ 212 - 0
src/res/tablek.json

@@ -0,0 +1,212 @@
+{
+    "key": [
+        "包一_0",
+		"一包_0",
+		"二包_0",
+		"三包_0",
+		"四包_0",
+		"五包_0",
+		"六包_0",
+		"七包_0",
+		"八包_0",
+		"九包_0",
+        "包二_0",
+        "包三_0",
+        "包四_0",
+        "包五_0",
+		"包六_0",
+		"包七_0",
+        "包1_0",
+        "包2_0",
+        "包3_0",
+        "包4_0",
+		"包5_0",
+		"包6_0",
+		"包7_0",
+		"包8_0",
+		"成交明细",
+        "合同1_0",
+        "合同一_0",
+        "分包1_0",
+        "分包一_0",
+        "子包1_0",
+        "子包一_0",
+        "标段1_0",
+        "标段2_0",
+        "标段3_0",
+        "标段4_0",
+        "标段一_0",
+        "标段二_0",
+        "标段三_0",
+        "标段四_0",
+        "标段五_0",
+        "一标段_0",
+        "二标段_0",
+        "三标段_0",
+        "四标段_0",
+        "五标段_0",
+        "六标段_0",
+        "七标段_0",
+        "1标段_0",
+        "2标段_0",
+        "3标段_0",
+        "4标段_0",
+        "5标段_0",
+        "6标段_0",
+        "7标段_0",
+        "符合标准_9",
+        "要求时间_9",
+        "设计院_9",
+        "研究院_9",
+        "有限公司_9",
+        "责任公司_9",
+        "研究所_9",
+        "事务所_9",
+        "协会_9",
+        "中心_9",
+        "集团_9",
+        "元整_9",
+        "首选_00",
+        "第一_00",
+        "第二_00",
+        "第三_00",
+        "第1_00",
+        "第2_00",
+        "第3_00",
+        "序号_1",
+        "包号_1",
+        "列号_1",
+        "品目号_1",
+        "标段_1",
+        "分包_1",
+        "技术标准",
+        "证书编号",
+        "行业",
+        "注册资金",
+        "企业银行账户信息",
+        "开户",
+        "账号",
+        "编号_1",
+        "品目名称_1",
+        "公告标题",
+        "项目信息",
+        "采购品目",
+        "招标代码",
+        "商品分类_1",
+        "公告代码",
+        "采购代码",
+        "项目代码",
+        "采购内容",
+        "项目明细",
+        "内容",
+        "价格",
+        "金额",
+        "预算",
+        "文件费",
+        "公司名称",
+        "单位性质",
+        "行业",
+        "邮箱",
+        "机构代码",
+		"证号",
+		"购买人",
+        "总价",
+        "投标价",
+        "费率",
+        "报价",
+        "项目内容",
+        "中标_2",
+        "供应商名称_2",
+        "成交供应商_2",
+        "名单",
+        "招标人_1",
+        "采购人_1",
+        "投标人_2",
+        "招标方",
+        "发布机构",
+        "经办人",
+        "负责人",
+        "地址",
+        "联系",
+        "备注",
+        "评标",
+        "设备名称",
+        "暂估价",
+        "依据",
+        "文件条款",
+        "品牌",
+        "限价",
+        "控制价",
+        "数量",
+        "单价",
+        "项目名称",
+		"使用单位",
+		"省级一级预算单位",
+		"专家1论证意见",
+		"专家2论证意见",
+		"专家3论证意见",
+		"专家4论证意见",
+		"专家5论证意见",
+        "工程名称",
+        "货物名称",
+        "建设名称",
+        "单位名称",
+        "商品名称",
+        "计量单位",
+        "税率",
+        "比率",
+        "采购人",
+		"采购单位",
+        "代理机构",
+        "管理机构",
+        "方式",
+        "时间",
+        "公示期",
+        "公示期限",
+        "评审",
+        "范围",
+        "情况",
+        "概况",
+        "日期",
+        "品名",
+        "规格",
+        "型号",
+        "参数",
+        "服务要求",
+        "指标",
+        "项目总监",
+        "项目经理",
+        "成交",
+        "评委",
+        "评分",
+        "评审",
+        "地点",
+        "原因",
+        "技术要求",
+        "附件",
+        "质保期",
+        "工期",
+        "用途",
+        "合计",
+        "电话",
+        "名次_2",
+        "投标单位_2",
+        "候选_2",
+        "得分_2",
+        "总分_2",
+        "排序_2",
+        "排名_2",
+        "身份证号码",
+        "职业资格证书",
+        "职务",
+        "姓名",
+        "类别",
+        "建设单位",
+        "招标方式",
+        "资格等级",
+        "法人代表",
+        "如有",
+        "业绩信息",
+        "获奖信息"
+    ]
+}

+ 68 - 0
src/res/tablev1.json

@@ -0,0 +1,68 @@
+{
+	"normalhead":[
+		"^((.{2,6}(名称|编号|代码|时间|类型|性质|行政区域|原因|项目|意见|须知|程度))|标段(编号)?|招标金额|规模|统一社会信用代码|拟?中标供应商|质量|(质量)?承诺|地址|招标代理|序号|材料|结构|结构层数|评委|单位|数量|排名|标的|标项|开户银行|邮编|账号|电话|传真|网址|得分|名次|包件?号|职务|(建设|招标|采购|中标|成交|甲|乙)(单位|人|供应商|方|规模).{0,2}|.{0,5}(价格?|额|资金|[预概]算|投资|费用|报价|投标价)(万?元?([大小]写)?))$__M",
+		"^.{0,7}(((单位)?名称|总监|经理|负责人|信息|率|费|期|人|方|号|码|(价格?|额|资金)(万?元?([大小]写)?)|员|品目|标包|代表|区域|方式|因素|合价|合计|小计|地点|条件|(资质|类别和)等级|类别|状态)|得分|注册专业|方法|家数|全称|简称|邮件|执业或职业资格|证书|部门|事项|来源|划分|长度|规模|保证金|目标)$__",
+		"(名单|证号|名称|要求|时间|日期|地点|单位|条款|机构|范围|情况|概况|品名|规格|参数|标准|指标|型号|限价|数量|方式|等级|依据|明细|概况|内容|性质|地区|地址|币种|主题|详情|说明|代理(公司|机构)|节支率|名单|结果|结果公示)$|^(职称|姓名|级别|职称专业|证书名称|证书编号)$__",
+		"^(联系|评标|单位|公告|采购|商品|附件|质保|用途|公示|机构|评审|品名|规格|参数|指标|型号|数量|证书).{0,10}$__",
+		"(专家|评委|打分)$__",
+		"品牌",
+        "姓名",
+		"起讫桩号",
+		"服务期",
+		"限价",
+		"邮编",
+		"面积",
+		"组织形式",
+		"招标方式",
+		"修建宽度",
+        "类别",
+        "备注",
+		"合计",
+        "电话",
+        "评审",
+		"原因",
+		"行业",
+		"价格",
+		"注册资金"
+	],
+	"jghead":[
+		"^.{0,2}[预拟]?(成交|中标|候选)(供应商|单位|企业|人|机构|价|金额).{0,2}$__M",
+		"^.{0,6}[打得评总](分)$__",
+		"(中标|磋商|投标|报|成交)总?(价|金额)__",
+		"(投标|中标)(人|方|单位|供应商)(名称)?__",
+		"成交",
+		"名次",
+	    "候选",
+		"业绩",
+		"荣誉",
+	    "排序",
+	    "排名",
+		"中标",
+		"供应商"
+	],
+	"con":[
+		"^((子|合同|分|施工|监理)?(标段?|包|合同段|标包|序号)[a-zA-Z0-9\\-一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)__$1",
+		"([a-zA-Z0-9\\-一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+(子|合同|分|施工|监理)?(标段?|包|合同段|标包|号))$__$1",
+		"(^[a-zA-Z0-9\\-一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+$)__$1",
+		"(^.{2,18}(集团|事务所|研究院|事务所|研究所|设计院))__",
+		"(^.{5,}(公司))__",
+		".{2,20}元整|[\\d]+万?元__",
+		".{4,}采购(项目)?__",
+		"(首选|第[一二三四五1-5])(顺序|推荐)?(中标|候选|成交)?(候选)?(人|单位|供应商)__BO"
+	],
+	"abandontable":[
+		"(磋商|谈判|评标(委员会)?)?((小组)?成员|(评审)?专家)(名单)?$__",
+		"(业绩|资质|原因|相关资料)$__",
+		"([废流落]标|评审)(原因|情况)__",
+		"(中标|成交)(候选)*(人|供应商|单位)((类似)*业绩|资质)__",
+		"否决投标情况",
+		"落标供应商及落标原因",
+		"被废标供应商名称",
+		"主要人员",
+		"其他投标人"
+	],
+	"bidorder":[
+		".{0,8}排[序名]$__sort",
+		"(人|供应商|单位)(名称)?$__entname"
+	]
+}

+ 24 - 0
src/web/res/bower_components/select2/.bower.json

@@ -0,0 +1,24 @@
+{
+  "name": "select2",
+  "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
+  "main": [
+    "dist/js/select2.js",
+    "src/scss/core.scss"
+  ],
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git@github.com:select2/select2.git"
+  },
+  "homepage": "https://github.com/ivaynberg/select2",
+  "version": "4.0.5",
+  "_release": "4.0.5",
+  "_resolution": {
+    "type": "version",
+    "tag": "4.0.5",
+    "commit": "ebf10c93db7d6d7a0d1330119d4c6f32cbd231d7"
+  },
+  "_source": "https://github.com/ivaynberg/select2.git",
+  "_target": "^4.0.3",
+  "_originalSource": "select2"
+}

+ 6 - 0
src/web/res/bower_components/select2/.editorconfig

@@ -0,0 +1,6 @@
+[*]
+indent_style = space
+end_of_line = lf
+
+[*.js]
+indent_size = 2

+ 203 - 0
src/web/res/bower_components/select2/.github/CONTRIBUTING.md

@@ -0,0 +1,203 @@
+# Guidelines for Getting Help with Select2
+
+**Before** you open a new issue, you must **read these guidelines.**  If it is evident from your issue that you failed to research your question properly, **your issue may be closed** without being answered.
+
+## Troubleshooting
+
+There are some common problems that developers may encounter when using working with Select2 for the first time.  **If you are new to Select2**, please consult the [common problems](https://select2.org/troubleshooting/common-problems) section of the documentation first.
+
+If you don't find what you're looking for there, then please check the the [forums](https://forums.select2.org), [Stack Overflow](https://stackoverflow.com/questions/tagged/jquery-select2) and [existing issues](https://github.com/select2/select2/issues?utf8=%E2%9C%93&q=is%3Aissue), both opened and closed.  Your question may have already been asked and answered before!
+
+## Asking for Help
+
+In general, the Github issue tracker should **only** be used for **bug reports** and **feature requests**.  If you're just having trouble getting something to work, you should ask in the [forums](https://forums.select2.org) or on [Stack Overflow](https://stackoverflow.com) instead. 
+
+### Rules for all community platforms
+
+1. Be polite, civil and respectful.  Select2 is built and maintained entirely by volunteers.
+2. If you are not conversationally proficient in English, **do not just post a machine translation** (e.g. Google Translate). Post in your native language, so that others who speak your language can help. You may post a machine translation below it for the general community to decipher.
+3. Any code snippets should be formatted using [Markdown code fences](https://learn.userfrosting.com/troubleshooting/getting-help#use-markdown-to-format-blocks-of-code) and properly indented. Poorly formatted code is difficult to read and reflects badly on you as a programmer.
+4. Check what you write for spelling and grammar issues. If you want others to take the time to read your question carefully, you must write your question carefully.
+5. When possible create and link to a [minimal, complete, and verifiable](https://stackoverflow.com/help/mcve) example by cloning our [JSBin template](http://jsbin.com/goqiqolete/edit?html,js,output). Code dumps, zip files, etc are NOT acceptable.
+6. Include relevant screenshots or animations, if possible. Drag your screenshots directly into the forums or issue tracker text box. They will automatically be uploaded and the Markdown to display them will be generated.  Avoid third-party image hosts, or links which require extra clicks to view the image (except IRC, where this is unavoidable).
+
+### Stack Overflow
+
+Tag your question with the `jquery-select2` tag, and optionally with tags relevant to other technologies involved, such as `jquery` or another client-side framework.  You should also mention the version of Select2 that you are using.
+
+### GitHub Issue Tracker
+
+We really appreciate clear bug reports that _consistently_ show an issue _within Select2_.  If you are reporting a bug, you **must** follow these steps:
+
+1. Make sure that your issue is a bug or feature request.  General usage and troubleshooting questions should be directed to the [forums](https://forums.select2.org) or [Stack Overflow](https://stackoverflow.com/questions/tagged/jquery-select2).  Issues asking for general support WILL BE CLOSED automatically.
+2. Search the current issues, both open and closed, for a similar issue. If the bug is still present but the relevant issue has been closed, you may ask us to reopen the issue. Duplicate issues will be closed automatically.
+3. Make sure that you are using the latest stable version of Select2 (see the [release history](https://github.com/select2/select2/releases)). Old minor/patch versions will not be supported.
+4. State the steps needed to reproduce the problem.
+5. Report any errors in detail.  Vague issues like "it doesn't work when I do this" are not helpful.  Show that you have put some effort into identifying the cause of the error.  Check your [browser console](https://learn.userfrosting.com/troubleshooting/debugging#client-side-debugging) for any Javascript error messages.
+6. Mention your version of Select2, as well as the browser(s) and operating system(s) in which the problem occurs.
+
+Requesting features in Select2
+------------------------------
+Select2 is a large library that carries with it a lot of functionality. Because
+of this, many feature requests will not be implemented in the core library.
+
+Before starting work on a major feature for Select2, **post to the [forums](https://forums.select2.org) first** or you may risk spending a considerable amount of
+time on something which the project developers are not interested in bringing
+into the project.
+
+# Contributing to Select2
+
+Select2 is made up of multiple submodules that all come together to make the
+standard and extended builds that are available to users. The build system uses
+Node.js to manage and compile the submodules, all of which is done using the
+Grunt build system.
+
+### Installing development dependencies
+
+Select2 can be built and developed on any system which supports Node.js. You can download Node.js at
+[their website][nodejs].
+
+All other required Node.js packages can be installed using [npm][npm], which comes bundled alongside Node.js.
+
+```bash
+cd /path/to/select2/repo
+npm install
+```
+
+You may need to install `libsass` on your system if it is not already available in order to build the SASS files which generate the CSS for themes and the main component.
+
+### Building the Select2 component
+
+Select2 uses the [Grunt][grunt] build task system and defines a few custom
+tasks for common routines. One of them is the `compile` task, which compiles
+the JavaScript and CSS and produces the final files.
+
+```bash
+cd /path/to/select2/repo
+grunt compile
+```
+
+You can also generate the minified versions (`.min.js` files) by executing the
+`minify` task after compiling.
+
+```bash
+cd /path/to/select2/repo
+grunt minify
+```
+
+### Running tests
+
+Select2 uses the QUnit test system to test individual components.
+
+```bash
+cd /path/to/selct2/repo
+grunt test
+```
+
+### Setting up the documentation repo
+
+The documentation for Select2 is maintained in a [separate repository](https://github.com/select2/docs). Select2.org is built with the flat-file CMS [Grav](http://getgrav.org), using their [RTFM skeleton](https://github.com/getgrav/grav-skeleton-rtfm-site#rtfm-skeleton).
+
+The documentation files themselves are written in Markdown, and can be found in the `pages/` subdirectory. You can submit pull requests to the `develop` branch of the repo.
+
+If you'd like to set up a local instance of the entire documentation website, you must first have a web server (Nginx, Apache, etc) and PHP installed locally. Then, follow these steps:
+
+#### Step 1 - Install Grav
+
+This application uses the [Grav](https://learn.getgrav.org/) CMS.  This repository does not contain a full Grav installation - rather, it just contains the contents of Grav's `user` directory, which is where all of our content, themes, and assets live.  This was done as per the [recommendation on Grav's blog](https://getgrav.org/blog/developing-with-github-part-2), to make it easier to deploy changes to the live server.
+
+To install this website on your computer, first [install grav core](https://getgrav.org/downloads) in a project folder called `select2-docs` under your webserver's document root folder. Then, find the `user` folder inside of your project folder.  Delete the contents of the `user` folder and clone this repository directly into the user folder.
+
+When you're done it might look something like this:
+
+```
+htdocs/
+└── select2-docs/
+   ├── assets/
+   ├── ...
+   ├── user/
+       ├── .git
+       ├── accounts/
+       ├── assets/
+       ├── config/
+       └── ...
+   └── ...
+
+```
+
+#### Step 2
+
+Grav needs your webserver to be able to write to certain directories.  Make sure that `backup/`, `cache/`, `images/`, `logs/`, and `tmp/` are all writeable by the user account under which your webserver runs.
+
+#### Step 3
+
+Visit the local installation in your browser!  For example, [http://localhost/select2-docs](http://localhost/select2-docs).
+
+### Submitting a pull request
+
+We use GitHub's pull request system for submitting patches. Here are some
+guidelines to follow when creating the pull request for your fix.
+
+1. Make sure to create a ticket for your pull request. This will serve as the
+bug ticket, and any discussion about the bug will take place there. Your pull
+request will be focused on the specific changes that fix the bug.
+2. Make sure to reference the ticket you are fixing within your pull request.
+This will allow us to close off the ticket once we merge the pull request, or
+follow up on the ticket if there are any related blocking issues.
+3. Explain why the specific change was made. Not everyone who is reviewing your
+pull request will be familiar with the problem it is fixing.
+4. Run your tests first. If your tests aren't passing, the pull request won't
+be able to be merged. If you're breaking existing tests, make sure that you
+aren't causing any breaking changes.
+5. Only include source changes (`src/`). Do not make changes directly to files in the `dist`
+directory.
+
+By following these steps, you will make it easier for your pull request to be
+reviewed and eventually merged.
+
+Triaging issues and pull requests
+---------------------------------
+Anyone can help the project maintainers triage issues and review pull requests.
+
+### Handling new issues
+
+Select2 regularly receives new issues which need to be tested and organized.
+
+When a new issue that comes in that is similar to another existing issue, it
+should be checked to make sure it is not a duplicate.  Duplicates issues should
+be marked by replying to the issue with "Duplicate of #[issue number]" where
+`[issue number]` is the url or issue number for the existing issue.  This will
+allow the project maintainers to quickly close off additional issues and keep
+the discussion focused within a single issue.
+
+If you can test issues that are reported to Select2 that contain test cases and
+confirm under what conditions bugs happen, that will allow others to identify
+what causes a bug quicker.
+
+### Reviewing pull requests
+
+It is very common for pull requests to be opened for issues that contain a clear
+solution to the problem.  These pull requests should be rigorously reviewed by
+the community before being accepted.  If you are not sure about a piece of
+submitted code, or know of a better way to do something, do not hesitate to make
+a comment on the pull request.
+
+### Reviving old tickets
+
+If you come across tickets which have not been updated for a while, you are encouraged to revive them. If you do, please include more information in your comment on the issue. Common bugs and feature requests are more likely to be fixed, whether it is by the community or the
+developers, so keeping tickets up to date is encouraged.
+
+Licensing
+---------
+
+It should also be made clear that **all code contributed to Select** must be
+licensable under the [MIT license][licensing].  Code that cannot be released
+under this license **cannot be accepted** into the project.
+
+[grunt]: http://gruntjs.com/
+[isolated-case]: http://css-tricks.com/6263-reduced-test-cases/
+[issue-search]: https://github.com/select2/select2/search?q=&type=Issues
+[issue-tracker]: https://github.com/select2/select2/issues
+[licensing]: https://github.com/select2/select2/blob/master/LICENSE.md
+[nodejs]: https://nodejs.org/
+[npm]: https://www.npmjs.com/

+ 20 - 0
src/web/res/bower_components/select2/.github/ISSUE_TEMPLATE.md

@@ -0,0 +1,20 @@
+
+STOP!
+
+Before you open an issue:
+
+1. Make sure that your issue is a BUG or FEATURE REQUEST.  General usage and troubleshooting questions should be directed to the [forums](https://forums.select2.org) or [Stack Overflow](https://stackoverflow.com/questions/tagged/jquery-select2).  Issues asking for general support WILL BE CLOSED automatically.
+2. Search the current issues, both open and closed, for a similar issue. If the bug is still present but the relevant issue has been closed, you may ask us to reopen the issue. Duplicate issues will be closed automatically.
+3. Make sure that you are using the latest stable version of Select2 (see the [release history](https://github.com/select2/select2/releases)). Old minor/patch versions will not be supported.
+
+If you are CERTAIN that it is appropriate to open a new issue, you must:
+
+1. Create and link to a [minimal, complete, and verifiable](https://stackoverflow.com/help/mcve) example by cloning our [JSBin template](http://jsbin.com/goqiqolete/edit?html,js,output). Code dumps, zip files, etc are NOT acceptable.
+2. Any additional code snippets should be formatted using [Markdown code fences](https://learn.userfrosting.com/troubleshooting/getting-help#use-markdown-to-format-blocks-of-code) and properly indented. Poorly formatted code is difficult to read and reflects badly on you as a programmer.
+3. Include relevant screenshots or animations, if possible. Drag your screenshots directly into this text box. They will automatically be uploaded and the Markdown to display them will be generated.  Avoid third-party image hosts, or links which require extra clicks to view the image.
+4. If you are not conversationally proficient in English, do NOT just post a machine translation (e.g. Google Translate) to GitHub. Get help in crafting your question, either via the [forums](https://forums.select2.org) or in [chat](https://webchat.freenode.net/?channels=select2). If all else fails, you may post your bug report or feature request in your native language, with a machine translation below that. We will tag it with `translation-needed` so that others who speak your language can find it.
+5. State the steps needed to reproduce the problem.
+6. Mention your version of Select2, as well as the browser(s) and operating system(s) in which the problem occurs.
+7. Clear out this placeholder text.
+
+Issues that ignore these rules will be closed without further comment!

+ 13 - 0
src/web/res/bower_components/select2/.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,13 @@
+This pull request includes a
+
+- [ ] Bug fix
+- [ ] New feature
+- [ ] Translation
+
+The following changes were made
+
+-
+-
+-
+
+If this is related to an existing ticket, include a link to it as well.

+ 3 - 0
src/web/res/bower_components/select2/.gitignore

@@ -0,0 +1,3 @@
+node_modules
+dist/js/i18n/build.txt
+.sass-cache

+ 4 - 0
src/web/res/bower_components/select2/.jshintignore

@@ -0,0 +1,4 @@
+src/js/banner.*.js
+src/js/wrapper.*.js
+tests/vendor/*.js
+tests/helpers.js

+ 25 - 0
src/web/res/bower_components/select2/.jshintrc

@@ -0,0 +1,25 @@
+{
+  "bitwise": true,
+  "camelcase": true,
+  "curly": true,
+  "es3": true,
+  "eqnull": true,
+  "freeze": true,
+  "globals": {
+    "console": false,
+    "define": false,
+    "document": false,
+    "MockContainer": false,
+    "module": false,
+    "QUnit": false,
+    "require": false,
+    "test": false,
+    "window": false
+  },
+  "indent": 2,
+  "maxlen": 80,
+  "noarg": true,
+  "nonew": true,
+  "quotmark": "single",
+  "undef": true
+}

+ 22 - 0
src/web/res/bower_components/select2/.travis.yml

@@ -0,0 +1,22 @@
+language: node_js
+
+sudo: false
+
+node_js:
+  - 0.10
+
+env:
+  global:
+    - secure: XMNK8GVxkwKa6oLl7nJwgg/wmY1YDk5rrMd+UXz26EDCsMDbiy1P7GhN2fEiBSLaQ7YfEuvaDcmzQxTrT0YTHp1PDzb2o9J4tIDdEkqPcv1y8xMaYDfmsN0rBPdBwZEg9H5zUgi7OdUbrGswSYxsKCE3x8EOqK89104HyOo1LN4=
+    - secure: BU5BPRx6H4O3WJ509YPixjUxg+hDF3z2BVJX6NiGmKWweqvCEYFfiiHLwDEgp/ynRcF9vGVi1V4Ly1jq7f8NIajbDZ5q443XchZFYFg78K/EwD5mK6LYt16zb7+Jn0KbzwHeGRGzc9AvcEYlW6i634cSCm4n3BnqtF5PpogSzdw=
+
+script:
+  - grunt ci
+
+notifications:
+  email: false
+  irc:
+    channels:
+      - "chat.freenode.net#select2"
+    on_success: change
+    on_failure: always

+ 272 - 0
src/web/res/bower_components/select2/CHANGELOG.md

@@ -0,0 +1,272 @@
+# Change Log
+
+## 4.0.5
+
+### Bug fixes
+- Replace `autocapitalize=off` with `autocapitalize=none` (#4994)
+
+### Translations
+- Vietnamese: remove an unnecessary quote mark (#5059)
+- Czech: Add missing commas and periods (#5052)
+- Spanish: Update the 'errorLoading' message (#5032)
+- Fix typo in Romanian (#5005)
+- Improve French translation (#4988)
+- Add Pashto translation (`ps`) (#4960)
+- Add translations for lower and upper Sorbian (`dsb` and `hsb`) (#4949)
+- Updates to Slovak (#4915)
+- Fixed Norwegian `inputTooShort` message (#4817, 4896)
+- Add Afrikaans translation (`af`) (#4850)
+- Add Bosnian translation (`bs`) (#4504)
+
+## 4.0.4
+
+### New features / Improvements
+- Make tag matching case insensitive [https://github.com/select2/select2/commit/cb9a90457867ffb14c7b1550bb67e872e0a5c2dd, https://github.com/select2/select2/commit/1167bace78cd3b1a918c1b04f3bac54674eab62b]
+- Support selecting options with blank or `0` option values [https://github.com/select2/select2/commit/16b4840c0e2df0461998e3b464ee0a546173950d, https://github.com/select2/select2/commit/0358ee528765157234643d289bce6b8ca5889c72]
+
+### Bug fixes
+- Fix issue with entire form losing focus when tabbing away from a Select2 control (#4419)
+- Fix UMD support for CommonJS [https://github.com/select2/select2/commit/45a877345482956021161203ac789c25f40a7d5e]
+
+### Documentation
+- Github Pages documentation has been deprecated, replaced with https://github.com/select2/docs
+- Add django-autocomplete-light to integrations [https://github.com/select2/select2/pull/4597]
+- Correct typo in options page [https://github.com/select2/select2/pull/4389]
+- Correct misspelling in AJAX query parameters example [https://github.com/select2/select2/pull/4435]
+- "highlight" should be "focus" in focus example [https://github.com/select2/select2/pull/4441]
+- Correct misspelling in `<select>` serialization example [https://github.com/select2/select2/pull/4538]
+- Correct typos in documentation [https://github.com/select2/select2/pull/4663]
+
+### Translations
+- Add `errorLoading` Hungarian translation [https://github.com/select2/select2/commit/7d1d13352321e21670ff1c6cba7413aa264fd57a]
+- Add `errorLoading` German translation [https://github.com/select2/select2/commit/4df965219ea4c39147fde9335bc260840465933a]
+- Add Slovene language [https://github.com/select2/select2/commit/8e6422c570a87da8d89c45daf0d253695a943c84]
+- Add `errorLoading` Galician translation [https://github.com/select2/select2/commit/8fcc6202c37f4e06d951342bf142a3b906b6b8e3]
+- Add `errorLoading` Thai translation [https://github.com/select2/select2/commit/625fc78ee616baedf64aa37357403b4b72c7363c]
+- Add `searching` and `errorLoading` Finnish translations [https://github.com/select2/select2/pull/4730]
+- Add `errorLoading` Turkish translation [https://github.com/select2/select2/commit/fd4a0825315c7055347726d5818c999279f96ff8, https://github.com/select2/select2/commit/751b36767f9f28b9de9428d5e8035c9a404915d9]
+- Add Armenian language [https://github.com/select2/select2/commit/f6fa52dcc02341df1523f50348f2effc54ee2911]
+
+## 4.0.3
+
+This is the third bugfix release of Select2 4.0.0. It builds upon the [second bugfix release](https://github.com/select2/select2/releases/tag/4.0.2) and fixes many common issues.
+
+### New features / Improvements
+- The old `dropdownAutoWidth` option now properly works [https://github.com/select2/select2/commit/fe26b083eb830836061de1458e483782cefef424]
+- A `focus` event on the original `<select>` is now handled [https://github.com/select2/select2/commit/31e7a1d4c52ed7477769fcad5d15166ae3c9b4d0]
+- Adding and removing options now refreshes the selection automatically [https://github.com/select2/select2/commit/ea79a197e0ffe55aa600eed6d18cbd1c804c3176]
+
+### Bug fixes
+- `select2('option')` no longer mutate the arguments when working on multiple elements [https://github.com/select2/select2/commit/c2c1aeef31c95c6df5545c900a4e1782d712497c]
+- Better detect aborted requests [https://github.com/select2/select2/commit/cfb66f5e4f71a56c46a6890c5dde4b7f24f11fa8]
+- New options are now properly created during tokenization [https://github.com/select2/select2/commit/3b8cd2e36990e695e4cb4b966c8658e7ca1574dc]
+- Fix positioning bug with non-static parents for the dropdown [https://github.com/select2/select2/pull/4267]
+- Infinite scrolling no longer resets the keyboard focus [https://github.com/select2/select2/commit/e897d008a672da262ba84cee2a144578696ada29, https://github.com/select2/select2/commit/9f581285d88128b29a01fc1e5fd2d445d610b553]
+- `selectOnClose` now works properly with `closeOnSelect` [https://github.com/select2/select2/commit/481c43883e23874e9c35879d173eb8cc5b994b12]
+- Apply `ajax.delay` to empty search terms as well [https://github.com/select2/select2/commit/4b9e02f02211248be25ac4c16d4635cf38237bb9]
+
+### Documentation
+- Added example for attaching event listeners [https://github.com/select2/select2/commit/84d6b5d840f7f4e6b7a2fb3f08424bf5495c876d]
+- Correct link to the [Select2 Bootstrap Theme](https://github.com/select2/select2-bootstrap-theme) [https://github.com/select2/select2/pull/4318]
+- Added example for using a `<label>` [https://github.com/select2/select2/commit/3bc7f4ac78b58eff8cd17b3273596638c3c9c5c1]
+- Add documentation for `ajax.url` [https://github.com/select2/select2/commit/5a831afb9a7d46e8f20aec21164cfbfd182024de]
+- Added favicon [https://github.com/select2/select2/pull/4379]
+
+### Translations
+- Add Khmer translation [https://github.com/select2/select2/pull/4246]
+- Added Norwegian bokmaal for `errorLoading` [https://github.com/select2/select2/pull/4259]
+- Fixed pluralization in Lithuanian translation [https://github.com/select2/select2/commit/5b5eddd183c87bf43165b3a98e03eabe10e9fa58]
+- Add French translation for `errorLoading` [https://github.com/select2/select2/commit/b1ea28bb7d8c02b3b352f558031ccfc8041122eb]
+- Add Greek translation [https://github.com/select2/select2/pull/4139]
+
+## 4.0.2
+
+This is the second bugfix release of Select2 4.0.0. It builds upon the [first release candidate of Select2 4.0.2](https://github.com/select2/select2/releases/tag/4.0.2-rc.1) with some minor improvements.
+
+### New features / Improvements
+
+- Added `insertTag` option to control the placement of the `tags` option [https://github.com/select2/select2/pull/4008]
+- Added handler for AJAX errors [https://github.com/select2/select2/issues/3501]
+- Added insertTag to control the tag position [https://github.com/select2/select2/pull/4008]
+
+### Bug fixes
+
+- Fixed positioning issues with static dropdown parents [https://github.com/select2/select2/issues/3970]
+- Fixed existing selections not always being respected with array data [https://github.com/select2/select2/issues/3990]
+- Sanitize automatically generated ids so CSS identifiers can be used [https://github.com/select2/select2/issues/3618]
+- Recursively apply defaults so AJAX defaults can be set [https://github.com/select2/select2/commit/983cd8e765c5345bfe7d3bdcc3b0c882a35461ca]
+- No need to recalculate the top of the dropdown twice [https://github.com/select2/select2/pull/4155]
+
+### Documentation
+
+- Updated Bootstrap and Font Awesome dependencies [https://github.com/select2/select2/commit/a5e539b509778eabeb8ce79e191b3ee1e81f6deb, https://github.com/select2/select2/commit/81a4a68b113e0d3e0fb1d0f8b1c33ae1b48ba04f, https://github.com/select2/select2/commit/6369f5f173fb81ec692213782945cc737e248da5]
+- Use Jekyll's highlighting instead of prettify [https://github.com/select2/select2/commit/54441e6a22be3969dd934ccb769f5d7dde684bfb, https://github.com/select2/select2/commit/74387b98632c75b06d15d83ad5359b9daf0f5dcb, https://github.com/select2/select2/commit/a126b53b4c90fac33b5d855894647cd8bcac3558, https://github.com/select2/select2/commit/75163d67cb80e4279965a97e9eeda5b171806085]
+- Corrected responsive width example to properly show it working [https://github.com/select2/select2/commit/63d531a9c0ab51f05327492a56f3245777762b45]
+- Replaced protocol-relative URLs with HTTPS protocol [https://github.com/select2/select2/pull/4127]
+- Code snippets for mapping `id` and `text` [https://github.com/select2/select2/issues/4086]
+- Document how to trigger `change` just for Select2 [https://github.com/select2/select2/issues/3620]
+- Added notes about DOM events [https://github.com/select2/select2/commit/37dbe059fce4578b46b7561e6243b7fdc63ac002]
+
+### Translations
+- Correct Romanian translation [https://github.com/select2/select2/commit/72d905f9e026d49e7c600f37a1ce742c404654d7]
+
+## 4.0.1
+
+This is the first bugfix release of Select2 4.0.0. It builds upon the [first release candidate of Select2 4.0.1](https://github.com/select2/select2/releases/tag/4.0.1-rc.1) with some minor improvements.
+
+### New features / improvements
+- The option container is now passed in as the second argument when templating selections using `templateResult` [https://github.com/select2/select2/commit/dc516e7073605723be59bc727b96a3b3dea1ae5a]
+- The option container is now passed in as the second argument when templating selections using `templateSelection` [https://github.com/select2/select2/pull/3324]
+- You can immediately start typing to search when tabbing into a multiple select [https://github.com/select2/select2/commit/02cca7baa7b78e73cdcf393172ee3a54be387167, https://github.com/select2/select2/commit/79cdcc0956e242c1ce642bbaa93e538c54f4be0]
+- All parameters passed in for AJAX requests are now set as query string parameters by default [https://github.com/select2/select2/issues/3548]
+
+### Bug fixes
+- The search box will now be properly sized after removing a selection [https://github.com/select2/select2/commit/5f80c5d9f81f3c5398c3e6e3e84fd6c67c8873f1]
+- Dropdown results will now be spoken by screen readers [https://github.com/select2/select2/commit/9fae3d74e373fc646da4e39a0c2ab11efa808c3f]
+- Options are now properly cloned when initializing multiple instances at once [https://github.com/select2/select2/commit/3c8366e8769233a6b20ade934fe629279e7be6ff]
+- `selectOnClose` and now be used with `closeOnSelect` without getting a stack overflow [https://github.com/select2/select2/commit/393ca4cf7f7f7097d3a994bda3dbf195e945eba1]
+- Fixed positioning with non-static parents [https://github.com/select2/select2/commit/c9216b4b966653dd63a67e815b47899ef5325298]
+- Fixed bug where multiple selects with placeholders were buggy in IE [https://github.com/select2/select2/issues/3300]
+- Fixed bug where AJAX selects could not be initialized with array data [https://github.com/select2/select2/pull/3375]
+- `:all:` is now correctly removed when used in `containerCss` and `dropdownCss` options [https://github.com/select2/select2/pull/3464]
+- Fixed bug where the multiple select search box would appear on the left in RTL mode [https://github.com/select2/select2/pull/3502]
+- Change ALT + UP to close the dropdown instead of opening it [https://github.com/select2/select2/commit/d2346cc33186c2a00fa2dad29e8e559c42bfea00]
+- Fix focus issue with the multiple select search box when the `change` event was triggered [https://github.com/select2/select2/commit/698fe7b9e187e182f679aa679eb8b0ecb64a846b, https://github.com/select2/select2/commit/88503d2c67dc7f4fb9395a17f17edfe4948cf738, https://github.com/select2/select2/commit/dd2990adead92593a2dffff6ae004ea8b647d130]
+- Fix bug in `ArrayAdapter` where the existing `<option>` data would be used instead of the array data [https://github.com/select2/select2/pull/3565]
+- Remove random call to `$dropdownContainer.width()` in the `AttachBody` decorator [https://github.com/select2/select2/pull/3654]
+- Fix memory leak in `AttachBody` decorator [https://github.com/select2/select2/commit/671f5a2ce21005090e0b69059799cd3dd1fbbf84]
+- Selections can no longer be removed when Select2 is in a disabled state [https://github.com/select2/select2/commit/68d068f1d2c7722d011d285a291d1f974bf09772, https://github.com/select2/select2/commit/7d8f86cbf85ebd2179195ff6a2a7a1c5dcb9da58]
+- Remove redundant `open` event trigger [https://github.com/select2/select2/pull/3507]
+- Correct references to `this` in `ajax.data` and `ajax.url` callback functions [https://github.com/select2/select2/issues/3361]
+- Apply select2('option') calls on all elements [https://github.com/select2/select2/pull/3495]
+
+### Design
+
+- Fixed original `<select>` not always being hidden correctly in some cases [https://github.com/select2/select2/pull/3301]
+- Fix potential issue with Bootstrap's gradients in Internet Explorer [https://github.com/select2/select2/pull/3307]
+- Improve compatibility with Zurb Foundation [https://github.com/select2/select2/pull/3290]
+- Remove padding on mobile safari search field in multiple selects [https://github.com/select2/select2/pull/3605]
+- Fix the clear button appearing beneath long text [https://github.com/select2/select2/issues/3306]
+- Migrate the CSS classes for the "Loading more results" message to BEM [https://github.com/select2/select2/issues/3889]
+- Fix inline search not displaying properly in Safari [https://github.com/select2/select2/issues/3459]
+
+### Documentation
+
+- New documentation theme designed by @fk [https://github.com/select2/select2/pull/3376, https://github.com/select2/select2/pull/3467, https://github.com/select2/select2/pull/3488]
+- Update ajax example to reflect pagination [https://github.com/select2/select2/pull/3357]
+- Fix incorrect option name in `maxiumSelectionLength` example [https://github.com/select2/select2/pull/3454]
+- Fix typos in the disabled mode/results examples [https://github.com/select2/select2/pull/3665]
+- Fix `Option` parameters in the 4.0 announcement [https://github.com/select2/select2/pull/3547]
+- Fix invalid JSON in the tags example within the 4.0 announcement [https://github.com/select2/select2/pull/3637]
+
+### Translations
+- Added Cyrillic variant of the Serbian language [https://github.com/select2/select2/pull/3943]
+- Corrected Thai "no results found" translation [https://github.com/select2/select2/pull/3782]
+- Swapped the `inputTooLong` and `inputTooShort` messages in the Galician translation [https://github.com/select2/select2/pull/3291]
+- Fix improper grammar in Dutch translation [https://github.com/select2/select2/pull/3692]
+- Add Japanese translation [https://github.com/select2/select2/pull/3477]
+- Polish translation: Fixed typo in maximum selected message [https://github.com/select2/select2/pull/3587]
+- Add Malay translation [https://github.com/select2/select2/pull/3635]
+- Add `errorLoading` for Indonesian translation [https://github.com/select2/select2/pull/3635]
+- Correct grammar issues in Hebrew translation [https://github.com/select2/select2/pull/3911]
+- Add `errorLoading` for Danish translation [https://github.com/select2/select2/pull/3870]
+- Add Arabic translation [https://github.com/select2/select2/pull/3859]
+
+## 4.0.0
+
+
+This builds upon [the second release candidate](https://github.com/select2/select2/tree/4.0.0-rc.2), **so review all previous release notes** before upgrading from previous versions of Select2.
+
+### Supported environments
+- jQuery 1.7.2+
+- Modern browsers (Chrome, Firefox, Safari)
+- Internet Explorer 8+
+
+### New features
+- Fully compatible with AMD and UMD based loaders.
+- Advanced plugin system that [uses custom adapters](https://select2.org/advanced/adapters-and-decorators).
+- Full support for `jQuery.noConflict`.
+- A `<select>` is the recommended element and [can be used for all options](https://select2.org/upgrading/migrating-from-35#no-more-hidden-input-tags).  There is limited backwards-compatible support for the `<input>` element in [full builds](https://select2.org/getting-started/builds-and-modules).
+- [Declarative configuration through `data-*` attributes](https://select2.org/configuration/data-attributes)
+- Easy to configure theme system and new default theme
+- You can use more specific locales (like `en-US`) and Select2 will be able to determine what translation files to load.
+
+### Breaking changes
+- Select2 now uses the MIT license
+- [The full build](https://select2.org/getting-started/builds-and-modules) of Select2 no longer includes jQuery - You must include jQuery separately on your page.
+- Select2 will prevent the inner scrolling of modals (and other scrollable containers) when it is open to prevent the UI from breaking. [Read more at the commit.](https://github.com/select2/select2/commit/003d6053a9fff587c688008397e7d5824463fe99)
+- jQuery is no longer listed as a dependency in the `bower.json`/`component.json` files.
+- [`<select>` has replaced `<input type="hidden" />`](https://select2.org/upgrading/migrating-from-35#no-more-hidden-input-tags) for **all options** (_including remote data_)
+- The [`matcher` has been revamped](https://select2.org/upgrading/migrating-from-35#advanced-matching-of-searches) to include full context, a compatibility module (`select2/compat/matcher`) has been created
+- The [display always reflects the order](https://select2.org/upgrading/migrating-from-35#display-reflects-the-actual-order-of-the-values) data is sent to the server
+- The click mask is no longer the default (again). You can get back the old functionality by wrapping your `selectionAdapter` with the `ClickMask` (`select2/selection/clickMask`) decorator.
+- Select2 no longer stops the propagation of events happening within the dropdown and selection. You can use the `StopPropagation` modules available in the [full builds](https://select2.org/getting-started/builds-and-modules) to prevent this. [https://github.com/select2/select2/commit/8f8140e3b00c5d5bb232455137c4c633d7da4275]
+- The enter key no longer toggles the state of multiple select items in the results, but instead will only select them. Use CTRL + Space instead to toggle the state. [https://github.com/select2/select2/commit/017c20109471fa5b835603faf5dc37f7c2c2ea45]
+- Warnings will now be triggered in the developer console if Select2 detects an unsupported configuration.
+
+#### Options
+
+- The default value of the `width` option has been changed from `style` to `resolve`.
+- The `copy` value for the `width` option has been renamed to `style`.
+
+##### Renamed
+- `formatSelection` -> `templateSelection`
+- `formatResult` -> `templateResult`
+- `sortResults` -> `sorter`
+- `createSearchChoice` -> `createTag`
+- `selectOnBlur` -> `selectOnClose`
+- `ajax.jsonpCallback` -> `ajax.jsonp`
+- `ajax.results` -> `ajax.processResults`
+- `tags: [array,of,data]` -> `data: [array,of,data], tags: true`
+- `placeholderOption` has been replaced by `placeholder.id` (`placeholder` -> `placeholder.text`)
+
+##### [Internationalization](https://select2.org/i18n)
+- `formatNoMatches` -> `language.noMatches`
+- `formatSearching` -> `language.searching`
+- `formatInputTooShort` -> `language.inputTooShort`
+- `formatInputTooLong` -> `language.inputTooLong`
+- `formatAjaxError` -> `language.errorLoading`
+- `formatLoading` -> `language.loadingMore`
+- `formatSelectionTooBig` -> `language.maximumSelected`
+
+##### Deprecated/Removed
+- `initSelection` - This is [no longer needed](https://select2.org/upgrading/migrating-from-35#removed-the-requirement-of-initselection) with `<select>` tags.  Limited backwards compatibility in the [full build](https://select2.org/getting-started/builds-and-modules).
+- `id` - Data objects should now always have `id` and `text` attributes that are strings, use [`$.map`](https://api.jquery.com/jquery.map/) when migrating
+- `query` - Use a [custom data adapter](https://select2.org/upgrading/migrating-from-35#custom-data-adapters-instead-of-query) instead.  Limited backwards compatibility in the [full build](https://select2.org/getting-started/builds-and-modules).
+- `ajax.params` - All parameters passed to `ajax` will be passed to the AJAX data transport function
+
+#### Methods
+
+##### Renamed
+- `.select2("val", [value])` -> `.val([value])`
+- `.select2("enable", !disabled)` -> `.prop("disabled", disabled)`
+
+##### Removed
+- `.select2("onSortStart")` and `.select2("onSortEnd")` - A custom [selection adapter](https://select2.org/advanced/default-adapters/selection) should be created instead
+- `.select2("data", data)` - Create the `<option>` tags for the objects that you would like to set, and set the `.val` to select them
+- `.select2("readonly")` - There is [no way to make a `<select>` element read-only](http://stackoverflow.com/q/368813/359284), disable it instead
+
+#### Events
+
+##### New
+- `select2:closing` is triggered before the dropdown is closed
+- `select2:select` is triggered when an option is selected
+
+##### Renamed
+- `select2-close` is now `select2:close`
+- `select2-open` is now `select2:open`
+- `select2-opening` is now `select2:opening`
+- `select2-selecting` is now `select2:selecting`
+- `select2-removed` is now `select2:unselect`
+- `select2-removing` is now `select2:unselecting`
+
+##### Removed
+- `select2-clearing` has been removed in favor of `select2:unselecting`
+- `select2-highlight`
+- `select2-loaded`
+- `select2-focus` - Use the native `focus` event instead
+- `select2-blur` - Use the native `blur` event instead
+- All extra properties from the `change` event were removed
+  - `val` can be retrieved with `$element.val()` instead
+  - `added` can be retrieved by listening to `select2:select`
+  - `removed` can be retrieved by listening to `select2:unselect`

+ 380 - 0
src/web/res/bower_components/select2/Gruntfile.js

@@ -0,0 +1,380 @@
+module.exports = function (grunt) {
+  // Full list of files that must be included by RequireJS
+  includes = [
+    'jquery.select2',
+    'almond',
+
+    'jquery-mousewheel' // shimmed for non-full builds
+  ];
+
+  fullIncludes = [
+    'jquery',
+
+    'select2/compat/containerCss',
+    'select2/compat/dropdownCss',
+
+    'select2/compat/initSelection',
+    'select2/compat/inputData',
+    'select2/compat/matcher',
+    'select2/compat/query',
+
+    'select2/dropdown/attachContainer',
+    'select2/dropdown/stopPropagation',
+
+    'select2/selection/stopPropagation'
+  ].concat(includes);
+
+  var i18nModules = [];
+  var i18nPaths = {};
+
+  var i18nFiles = grunt.file.expand({
+    cwd: 'src/js'
+  }, 'select2/i18n/*.js');
+
+  var testFiles = grunt.file.expand('tests/**/*.html');
+  var testUrls = testFiles.map(function (filePath) {
+    return 'http://localhost:9999/' + filePath;
+  });
+
+  var testBuildNumber = "unknown";
+
+  if (process.env.TRAVIS_JOB_ID) {
+    testBuildNumber = "travis-" + process.env.TRAVIS_JOB_ID;
+  } else {
+    var currentTime = new Date();
+
+    testBuildNumber = "manual-" + currentTime.getTime();
+  }
+
+  for (var i = 0; i < i18nFiles.length; i++) {
+    var file = i18nFiles[i];
+    var name = file.split('.')[0];
+
+    i18nModules.push({
+      name: name
+    });
+
+    i18nPaths[name] = '../../' + name;
+  }
+
+  var minifiedBanner = '/*! Select2 <%= package.version %> | https://github.com/select2/select2/blob/master/LICENSE.md */';
+
+  grunt.initConfig({
+    package: grunt.file.readJSON('package.json'),
+
+    clean: {
+      docs: ['docs/_site']
+    },
+
+    concat: {
+      'dist': {
+        options: {
+          banner: grunt.file.read('src/js/wrapper.start.js'),
+        },
+        src: [
+          'dist/js/select2.js',
+          'src/js/wrapper.end.js'
+        ],
+        dest: 'dist/js/select2.js'
+      },
+      'dist.full': {
+        options: {
+          banner: grunt.file.read('src/js/wrapper.start.js'),
+        },
+        src: [
+          'dist/js/select2.full.js',
+          'src/js/wrapper.end.js'
+        ],
+        dest: 'dist/js/select2.full.js'
+      }
+    },
+
+    connect: {
+      tests: {
+        options: {
+          base: '.',
+          hostname: '127.0.0.1',
+          port: 9999
+        }
+      }
+    },
+
+    uglify: {
+      'dist': {
+        src: 'dist/js/select2.js',
+        dest: 'dist/js/select2.min.js',
+        options: {
+          banner: minifiedBanner
+        }
+      },
+      'dist.full': {
+        src: 'dist/js/select2.full.js',
+        dest: 'dist/js/select2.full.min.js',
+        options: {
+          banner: minifiedBanner
+        }
+      }
+    },
+
+    qunit: {
+      all: {
+        options: {
+          urls: testUrls
+        }
+      }
+    },
+
+    'saucelabs-qunit': {
+      all: {
+        options: {
+          build: testBuildNumber,
+          tags: ['tests', 'qunit'],
+          urls: testUrls,
+          testTimeout: 8000,
+          testname: 'QUnit test for Select2',
+          browsers: [
+            {
+              browserName: 'internet explorer',
+              version: '8',
+              platform: 'Windows 7'
+            },
+            {
+              browserName: 'internet explorer',
+              version: '9',
+              platform: 'Windows 7'
+            },
+            {
+              browserName: 'internet explorer',
+              version: '10',
+              platform: 'Windows 7'
+            },
+
+            {
+              browserName: 'internet explorer',
+              version: '11',
+              platform: 'Windows 10'
+            },
+
+            {
+              browserName: 'firefox',
+              platform: 'linux'
+            },
+
+            {
+              browserName: 'chrome',
+              platform: 'linux'
+            },
+
+            {
+              browserName: 'opera',
+              version: '12',
+              platform: 'linux'
+            }
+          ]
+        }
+      }
+    },
+
+    'gh-pages': {
+      options: {
+        base: 'docs',
+        branch: 'master',
+        clone: 'node_modules/grunt-gh-pages/repo',
+        message: 'Updated docs with master',
+        push: true,
+        repo: 'git@github.com:select2/select2.github.io.git'
+      },
+      src: '**'
+    },
+
+    jekyll: {
+      options: {
+        src: 'docs',
+        dest: 'docs/_site'
+      },
+      build: {
+        d: null
+      },
+      serve: {
+        options: {
+          serve: true,
+          watch: true
+        }
+      }
+    },
+
+    jshint: {
+      options: {
+        jshintrc: true
+      },
+      code: {
+        src: ['src/js/**/*.js']
+      },
+      tests: {
+        src: ['tests/**/*.js']
+      }
+    },
+
+    sass: {
+      dist: {
+        options: {
+          outputStyle: 'compressed'
+        },
+        files: {
+          'dist/css/select2.min.css': [
+            'src/scss/core.scss',
+            'src/scss/theme/default/layout.css'
+          ]
+        }
+      },
+      dev: {
+        options: {
+          outputStyle: 'nested'
+        },
+        files: {
+          'dist/css/select2.css': [
+            'src/scss/core.scss',
+            'src/scss/theme/default/layout.css'
+          ]
+        }
+      }
+    },
+
+    symlink: {
+      docs: {
+        cwd: 'dist',
+        expand: true,
+        overwrite: false,
+        src: [
+          '*'
+        ],
+        dest: 'docs/dist',
+        filter: 'isDirectory'
+      }
+    },
+
+    requirejs: {
+      'dist': {
+        options: {
+          baseUrl: 'src/js',
+          optimize: 'none',
+          name: 'select2/core',
+          out: 'dist/js/select2.js',
+          include: includes,
+          namespace: 'S2',
+          paths: {
+            'almond': require.resolve('almond').slice(0, -3),
+            'jquery': 'jquery.shim',
+            'jquery-mousewheel': 'jquery.mousewheel.shim'
+          },
+          wrap: {
+            startFile: 'src/js/banner.start.js',
+            endFile: 'src/js/banner.end.js'
+          }
+        }
+      },
+      'dist.full': {
+        options: {
+          baseUrl: 'src/js',
+          optimize: 'none',
+          name: 'select2/core',
+          out: 'dist/js/select2.full.js',
+          include: fullIncludes,
+          namespace: 'S2',
+          paths: {
+            'almond': require.resolve('almond').slice(0, -3),
+            'jquery': 'jquery.shim',
+            'jquery-mousewheel': require.resolve('jquery-mousewheel').slice(0, -3)
+          },
+          wrap: {
+            startFile: 'src/js/banner.start.js',
+            endFile: 'src/js/banner.end.js'
+          }
+        }
+      },
+      'i18n': {
+        options: {
+          baseUrl: 'src/js/select2/i18n',
+          dir: 'dist/js/i18n',
+          paths: i18nPaths,
+          modules: i18nModules,
+          namespace: 'S2',
+          wrap: {
+            start: minifiedBanner + grunt.file.read('src/js/banner.start.js'),
+            end: grunt.file.read('src/js/banner.end.js')
+          }
+        }
+      }
+    },
+
+    watch: {
+      js: {
+        files: [
+          'src/js/select2/**/*.js',
+          'tests/**/*.js'
+        ],
+        tasks: [
+          'compile',
+          'test',
+          'minify'
+        ]
+      },
+      css: {
+        files: [
+          'src/scss/**/*.scss'
+        ],
+        tasks: [
+          'compile',
+          'minify'
+        ]
+      }
+    }
+  });
+
+  grunt.loadNpmTasks('grunt-contrib-clean');
+  grunt.loadNpmTasks('grunt-contrib-concat');
+  grunt.loadNpmTasks('grunt-contrib-connect');
+  grunt.loadNpmTasks('grunt-contrib-jshint');
+  grunt.loadNpmTasks('grunt-contrib-qunit');
+  grunt.loadNpmTasks('grunt-contrib-requirejs');
+  grunt.loadNpmTasks('grunt-contrib-symlink');
+  grunt.loadNpmTasks('grunt-contrib-uglify');
+  grunt.loadNpmTasks('grunt-contrib-watch');
+
+  grunt.loadNpmTasks('grunt-gh-pages');
+  grunt.loadNpmTasks('grunt-jekyll');
+  grunt.loadNpmTasks('grunt-saucelabs');
+  grunt.loadNpmTasks('grunt-sass');
+
+  grunt.registerTask('default', ['compile', 'test', 'minify']);
+
+  grunt.registerTask('compile', [
+    'requirejs:dist', 'requirejs:dist.full', 'requirejs:i18n',
+    'concat:dist', 'concat:dist.full',
+    'sass:dev'
+  ]);
+  grunt.registerTask('minify', ['uglify', 'sass:dist']);
+  grunt.registerTask('test', ['connect:tests', 'qunit', 'jshint']);
+
+  var ciTasks = [];
+
+  ciTasks.push('compile');
+  ciTasks.push('connect:tests');
+
+  /*
+  // grunt-saucelabs appears to be broken with Travis altogether now.
+  // Can't run Sauce Labs tests in pull requests
+  if (process.env.TRAVIS_PULL_REQUEST == 'false') {
+    ciTasks.push('saucelabs-qunit');
+  }
+  */
+
+  ciTasks.push('qunit');
+  ciTasks.push('jshint');
+
+  grunt.registerTask('ci', ciTasks);
+
+  grunt.registerTask('docs', ['symlink:docs', 'jekyll:serve']);
+
+  grunt.registerTask('docs-release', ['default', 'clean:docs', 'gh-pages']);
+};

+ 21 - 0
src/web/res/bower_components/select2/LICENSE.md

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 123 - 0
src/web/res/bower_components/select2/README.md

@@ -0,0 +1,123 @@
+Select2
+=======
+[![Build Status][travis-ci-image]][travis-ci-status]
+
+Select2 is a jQuery-based replacement for select boxes. It supports searching,
+remote data sets, and pagination of results.
+
+To get started, checkout examples and documentation at
+https://select2.org/
+
+Use cases
+---------
+* Enhancing native selects with search.
+* Enhancing native selects with a better multi-select interface.
+* Loading data from JavaScript: easily load items via AJAX and have them
+  searchable.
+* Nesting optgroups: native selects only support one level of nesting. Select2
+  does not have this restriction.
+* Tagging: ability to add new items on the fly.
+* Working with large, remote datasets: ability to partially load a dataset based
+  on the search term.
+* Paging of large datasets: easy support for loading more pages when the results
+  are scrolled to the end.
+* Templating: support for custom rendering of results and selections.
+
+Browser compatibility
+---------------------
+* IE 8+
+* Chrome 8+
+* Firefox 10+
+* Safari 3+
+* Opera 10.6+
+
+Select2 is automatically tested on the following browsers.
+
+[![Sauce Labs Test Status][saucelabs-matrix]][saucelabs-status]
+
+Usage
+-----
+You can source Select2 directly from a CDN like [JSDliver][jsdelivr] or
+[CDNJS][cdnjs], [download it from this GitHub repo][releases], or use one of
+the integrations below.
+
+Integrations
+------------
+Third party developers have created plugins for platforms which allow Select2 to be integrated more natively and quickly. For many platforms, additional plugins are not required because Select2 acts as a standard `<select>` box.
+
+Plugins
+
+* [Django]
+  - [django-autocomplete-light]
+  - [django-easy-select2]
+  - [django-select2]
+* [Meteor] - [meteor-select2]
+* [Ruby on Rails][ruby-on-rails] - [select2-rails]
+* [Wicket] - [wicketstuff-select2]
+* [Yii 2][yii2] - [yii2-widget-select2]
+
+Themes
+
+- [Bootstrap 3][bootstrap3] - [select2-bootstrap-theme]
+- [Flat UI][flat-ui] - [select2-flat-theme]
+- [Metro UI][metro-ui] - [select2-metro]
+
+Missing an integration? Modify this `README` and make a pull request back here to Select2 on GitHub.
+
+Internationalization (i18n)
+---------------------------
+Select2 supports multiple languages by simply including the right language JS
+file (`dist/js/i18n/it.js`, `dist/js/i18n/nl.js`, etc.) after
+`dist/js/select2.js`.
+
+Missing a language? Just copy `src/js/select2/i18n/en.js`, translate it, and
+make a pull request back to Select2 here on GitHub.
+
+Documentation
+-------------
+The documentation for Select2 is available
+[through GitHub Pages][documentation] and is located within this repository
+in the [`docs` folder][documentation-folder].
+
+Community
+---------
+You can find out about the different ways to get in touch with the Select2
+community at the [Select2 community page][community].
+
+Copyright and license
+---------------------
+The license is available within the repository in the [LICENSE][license] file.
+
+[cdnjs]: http://www.cdnjs.com/libraries/select2
+[community]: https://select2.org/getting-help
+[documentation]: https://select2.org
+[documentation-folder]: https://github.com/select2/select2/tree/master/docs
+[freenode]: https://freenode.net/
+[jsdelivr]: http://www.jsdelivr.com/#!select2
+[license]: LICENSE.md
+[releases]: https://github.com/select2/select2/releases
+[saucelabs-matrix]: https://saucelabs.com/browser-matrix/select2.svg
+[saucelabs-status]: https://saucelabs.com/u/select2
+[travis-ci-image]: https://img.shields.io/travis/select2/select2/master.svg
+[travis-ci-status]: https://travis-ci.org/select2/select2
+
+[bootstrap3]: https://getbootstrap.com/
+[django]: https://www.djangoproject.com/
+[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
+[django-easy-select2]: https://github.com/asyncee/django-easy-select2
+[django-select2]: https://github.com/applegrew/django-select2
+[flat-ui]: http://designmodo.github.io/Flat-UI/
+[meteor]: https://www.meteor.com/
+[meteor-select2]: https://github.com/nate-strauser/meteor-select2
+[metro-ui]: http://metroui.org.ua/
+[select2-metro]: http://metroui.org.ua/select2.html
+[ruby-on-rails]: http://rubyonrails.org/
+[select2-bootstrap-theme]: https://github.com/select2/select2-bootstrap-theme
+[select2-flat-theme]: https://github.com/techhysahil/select2-Flat_Theme
+[select2-rails]: https://github.com/argerim/select2-rails
+[vue.js]: http://vuejs.org/
+[select2-vue]: http://vuejs.org/examples/select2.html
+[wicket]: https://wicket.apache.org/
+[wicketstuff-select2]: https://github.com/wicketstuff/core/tree/master/select2-parent
+[yii2]: http://www.yiiframework.com/
+[yii2-widget-select2]: https://github.com/kartik-v/yii2-widget-select2

+ 13 - 0
src/web/res/bower_components/select2/bower.json

@@ -0,0 +1,13 @@
+{
+    "name": "select2",
+    "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
+    "main": [
+        "dist/js/select2.js",
+        "src/scss/core.scss"
+    ],
+    "license": "MIT",
+    "repository": {
+        "type": "git",
+        "url": "git@github.com:select2/select2.git"
+    }
+}

+ 19 - 0
src/web/res/bower_components/select2/component.json

@@ -0,0 +1,19 @@
+{
+  "name": "select2",
+  "repo": "select/select2",
+  "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
+  "version": "4.0.5",
+  "demo": "https://select2.org/",
+  "keywords": [
+    "jquery"
+  ],
+  "main": "dist/js/select2.js",
+  "styles": [
+    "dist/css/select2.css"
+  ],
+  "scripts": [
+    "dist/js/select2.js",
+    "dist/js/i18n/*.js"
+  ],
+  "license": "MIT"
+}

+ 25 - 0
src/web/res/bower_components/select2/composer.json

@@ -0,0 +1,25 @@
+{
+  "name": "select2/select2",
+  "description": "Select2 is a jQuery based replacement for select boxes.",
+  "type": "component",
+  "homepage": "https://select2.org/",
+  "license": "MIT",
+  "require": {
+    "robloach/component-installer": "*"
+  },
+  "extra": {
+    "component": {
+      "scripts": [
+        "dist/js/select2.js"
+      ],
+      "styles": [
+        "dist/css/select2.css"
+      ],
+      "files": [
+        "dist/js/select2.js",
+        "dist/js/i18n/*.js",
+        "dist/css/select2.css"
+      ]
+    }
+  }
+}

+ 484 - 0
src/web/res/bower_components/select2/dist/css/select2.css

@@ -0,0 +1,484 @@
+.select2-container {
+  box-sizing: border-box;
+  display: inline-block;
+  margin: 0;
+  position: relative;
+  vertical-align: middle; }
+  .select2-container .select2-selection--single {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    height: 28px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--single .select2-selection__rendered {
+      display: block;
+      padding-left: 8px;
+      padding-right: 20px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+    .select2-container .select2-selection--single .select2-selection__clear {
+      position: relative; }
+  .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
+    padding-right: 8px;
+    padding-left: 20px; }
+  .select2-container .select2-selection--multiple {
+    box-sizing: border-box;
+    cursor: pointer;
+    display: block;
+    min-height: 32px;
+    user-select: none;
+    -webkit-user-select: none; }
+    .select2-container .select2-selection--multiple .select2-selection__rendered {
+      display: inline-block;
+      overflow: hidden;
+      padding-left: 8px;
+      text-overflow: ellipsis;
+      white-space: nowrap; }
+  .select2-container .select2-search--inline {
+    float: left; }
+    .select2-container .select2-search--inline .select2-search__field {
+      box-sizing: border-box;
+      border: none;
+      font-size: 100%;
+      margin-top: 5px;
+      padding: 0; }
+      .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
+        -webkit-appearance: none; }
+
+.select2-dropdown {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  box-sizing: border-box;
+  display: block;
+  position: absolute;
+  left: -100000px;
+  width: 100%;
+  z-index: 1051; }
+
+.select2-results {
+  display: block; }
+
+.select2-results__options {
+  list-style: none;
+  margin: 0;
+  padding: 0; }
+
+.select2-results__option {
+  padding: 6px;
+  user-select: none;
+  -webkit-user-select: none; }
+  .select2-results__option[aria-selected] {
+    cursor: pointer; }
+
+.select2-container--open .select2-dropdown {
+  left: 0; }
+
+.select2-container--open .select2-dropdown--above {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--open .select2-dropdown--below {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-search--dropdown {
+  display: block;
+  padding: 4px; }
+  .select2-search--dropdown .select2-search__field {
+    padding: 4px;
+    width: 100%;
+    box-sizing: border-box; }
+    .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
+      -webkit-appearance: none; }
+  .select2-search--dropdown.select2-search--hide {
+    display: none; }
+
+.select2-close-mask {
+  border: 0;
+  margin: 0;
+  padding: 0;
+  display: block;
+  position: fixed;
+  left: 0;
+  top: 0;
+  min-height: 100%;
+  min-width: 100%;
+  height: auto;
+  width: auto;
+  opacity: 0;
+  z-index: 99;
+  background-color: #fff;
+  filter: alpha(opacity=0); }
+
+.select2-hidden-accessible {
+  border: 0 !important;
+  clip: rect(0 0 0 0) !important;
+  height: 1px !important;
+  margin: -1px !important;
+  overflow: hidden !important;
+  padding: 0 !important;
+  position: absolute !important;
+  width: 1px !important; }
+
+.select2-container--default .select2-selection--single {
+  background-color: #fff;
+  border: 1px solid #aaa;
+  border-radius: 4px; }
+  .select2-container--default .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--default .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold; }
+  .select2-container--default .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--default .select2-selection--single .select2-selection__arrow {
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px; }
+    .select2-container--default .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  left: 1px;
+  right: auto; }
+
+.select2-container--default.select2-container--disabled .select2-selection--single {
+  background-color: #eee;
+  cursor: default; }
+  .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
+    display: none; }
+
+.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
+  border-color: transparent transparent #888 transparent;
+  border-width: 0 4px 5px 4px; }
+
+.select2-container--default .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text; }
+  .select2-container--default .select2-selection--multiple .select2-selection__rendered {
+    box-sizing: border-box;
+    list-style: none;
+    margin: 0;
+    padding: 0 5px;
+    width: 100%; }
+    .select2-container--default .select2-selection--multiple .select2-selection__rendered li {
+      list-style: none; }
+  .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
+    color: #999;
+    margin-top: 5px;
+    float: left; }
+  .select2-container--default .select2-selection--multiple .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-top: 5px;
+    margin-right: 10px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
+    color: #999;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #333; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
+  float: right; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--default.select2-container--focus .select2-selection--multiple {
+  border: solid black 1px;
+  outline: 0; }
+
+.select2-container--default.select2-container--disabled .select2-selection--multiple {
+  background-color: #eee;
+  cursor: default; }
+
+.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
+  display: none; }
+
+.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--default .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa; }
+
+.select2-container--default .select2-search--inline .select2-search__field {
+  background: transparent;
+  border: none;
+  outline: 0;
+  box-shadow: none;
+  -webkit-appearance: textfield; }
+
+.select2-container--default .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--default .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--default .select2-results__option[aria-disabled=true] {
+  color: #999; }
+
+.select2-container--default .select2-results__option[aria-selected=true] {
+  background-color: #ddd; }
+
+.select2-container--default .select2-results__option .select2-results__option {
+  padding-left: 1em; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__group {
+    padding-left: 0; }
+  .select2-container--default .select2-results__option .select2-results__option .select2-results__option {
+    margin-left: -1em;
+    padding-left: 2em; }
+    .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+      margin-left: -2em;
+      padding-left: 3em; }
+      .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+        margin-left: -3em;
+        padding-left: 4em; }
+        .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+          margin-left: -4em;
+          padding-left: 5em; }
+          .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
+            margin-left: -5em;
+            padding-left: 6em; }
+
+.select2-container--default .select2-results__option--highlighted[aria-selected] {
+  background-color: #5897fb;
+  color: white; }
+
+.select2-container--default .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic .select2-selection--single {
+  background-color: #f7f7f7;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  outline: 0;
+  background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
+  background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+  .select2-container--classic .select2-selection--single:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--single .select2-selection__rendered {
+    color: #444;
+    line-height: 28px; }
+  .select2-container--classic .select2-selection--single .select2-selection__clear {
+    cursor: pointer;
+    float: right;
+    font-weight: bold;
+    margin-right: 10px; }
+  .select2-container--classic .select2-selection--single .select2-selection__placeholder {
+    color: #999; }
+  .select2-container--classic .select2-selection--single .select2-selection__arrow {
+    background-color: #ddd;
+    border: none;
+    border-left: 1px solid #aaa;
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+    height: 26px;
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    width: 20px;
+    background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
+    background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
+    background-repeat: repeat-x;
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
+    .select2-container--classic .select2-selection--single .select2-selection__arrow b {
+      border-color: #888 transparent transparent transparent;
+      border-style: solid;
+      border-width: 5px 4px 0 4px;
+      height: 0;
+      left: 50%;
+      margin-left: -4px;
+      margin-top: -2px;
+      position: absolute;
+      top: 50%;
+      width: 0; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
+  float: left; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
+  border: none;
+  border-right: 1px solid #aaa;
+  border-radius: 0;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+  left: 1px;
+  right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--single {
+  border: 1px solid #5897fb; }
+  .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
+    background: transparent;
+    border: none; }
+    .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
+      border-color: transparent transparent #888 transparent;
+      border-width: 0 4px 5px 4px; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+  background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+  background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
+  background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
+
+.select2-container--classic .select2-selection--multiple {
+  background-color: white;
+  border: 1px solid #aaa;
+  border-radius: 4px;
+  cursor: text;
+  outline: 0; }
+  .select2-container--classic .select2-selection--multiple:focus {
+    border: 1px solid #5897fb; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__rendered {
+    list-style: none;
+    margin: 0;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__clear {
+    display: none; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice {
+    background-color: #e4e4e4;
+    border: 1px solid #aaa;
+    border-radius: 4px;
+    cursor: default;
+    float: left;
+    margin-right: 5px;
+    margin-top: 5px;
+    padding: 0 5px; }
+  .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
+    color: #888;
+    cursor: pointer;
+    display: inline-block;
+    font-weight: bold;
+    margin-right: 2px; }
+    .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
+      color: #555; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  float: right; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
+  margin-left: 5px;
+  margin-right: auto; }
+
+.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
+  margin-left: 2px;
+  margin-right: auto; }
+
+.select2-container--classic.select2-container--open .select2-selection--multiple {
+  border: 1px solid #5897fb; }
+
+.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
+  border-top: none;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0; }
+
+.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
+  border-bottom: none;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0; }
+
+.select2-container--classic .select2-search--dropdown .select2-search__field {
+  border: 1px solid #aaa;
+  outline: 0; }
+
+.select2-container--classic .select2-search--inline .select2-search__field {
+  outline: 0;
+  box-shadow: none; }
+
+.select2-container--classic .select2-dropdown {
+  background-color: white;
+  border: 1px solid transparent; }
+
+.select2-container--classic .select2-dropdown--above {
+  border-bottom: none; }
+
+.select2-container--classic .select2-dropdown--below {
+  border-top: none; }
+
+.select2-container--classic .select2-results > .select2-results__options {
+  max-height: 200px;
+  overflow-y: auto; }
+
+.select2-container--classic .select2-results__option[role=group] {
+  padding: 0; }
+
+.select2-container--classic .select2-results__option[aria-disabled=true] {
+  color: grey; }
+
+.select2-container--classic .select2-results__option--highlighted[aria-selected] {
+  background-color: #3875d7;
+  color: white; }
+
+.select2-container--classic .select2-results__group {
+  cursor: default;
+  display: block;
+  padding: 6px; }
+
+.select2-container--classic.select2-container--open .select2-dropdown {
+  border-color: #5897fb; }

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
src/web/res/bower_components/select2/dist/css/select2.min.css


+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/af.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/af",[],function(){return{errorLoading:function(){return"Die resultate kon nie gelaai word nie."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Verwyders asseblief "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Voer asseblief "+t+" of meer karakters";return n},loadingMore:function(){return"Meer resultate word gelaai…"},maximumSelected:function(e){var t="Kies asseblief net "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"Geen resultate gevind"},searching:function(){return"Besig…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ar.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(e){var t=e.input.length-e.maximum;return"الرجاء حذف "+t+" عناصر"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"الرجاء إضافة "+t+" عناصر"},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(e){return"تستطيع إختيار "+e.maximum+" بنود فقط"},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/az.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/az",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return t+" simvol silin"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(e){return"Sadəcə "+e.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/bg.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bg",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Моля въведете с "+t+" по-малко символ";return t>1&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/bs.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bs",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspijelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ca.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/cs.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadejte o jeden znak méně.":n<=4?"Prosím, zadejte o "+e(n,!0)+" znaky méně.":"Prosím, zadejte o "+n+" znaků méně."},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadejte ještě jeden znak.":n<=4?"Prosím, zadejte ještě další "+e(n,!0)+" znaky.":"Prosím, zadejte ještě dalších "+n+" znaků."},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku.":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky.":"Můžete zvolit maximálně "+n+" položek."},noResults:function(){return"Nenalezeny žádné položky."},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/da.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Angiv venligst "+t+" tegn mindre"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Angiv venligst "+t+" tegn mere"},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/de.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{errorLoading:function(){return"Die Ergebnisse konnten nicht geladen werden."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/dsb.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/dsb",[],function(){var e=["znamuško","znamušce","znamuška","znamuškow"],t=["zapisk","zapiska","zapiski","zapiskow"],n=function(t,n){if(t===1)return n[0];if(t===2)return n[1];if(t>2&&t<=4)return n[2];if(t>=5)return n[3]};return{errorLoading:function(){return"Wuslědki njejsu se dali zacytaś."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Pšosym lašuj "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Pšosym zapódaj nanejmjenjej "+r+" "+n(r,e)},loadingMore:function(){return"Dalšne wuslědki se zacytaju…"},maximumSelected:function(e){return"Móžoš jano "+e.maximum+" "+n(e.maximum,t)+"wubraś."},noResults:function(){return"Žedne wuslědki namakane"},searching:function(){return"Pyta se…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/el.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/en.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/es.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/et.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/eu.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/fa.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها می‌توانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/fi.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{errorLoading:function(){return"Tuloksia ei saatu ladattua."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){return"Haetaan…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/fr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Supprimez "+t+" caractère"+(t>1)?"s":""},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Saisissez au moins "+t+" caractère"+(t>1)?"s":""},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){return"Vous pouvez seulement sélectionner "+e.maximum+" élément"+(e.maximum>1)?"s":""},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/gl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{errorLoading:function(){return"Non foi posíbel cargar os resultados."},inputTooLong:function(e){var t=e.input.length-e.maximum;return t===1?"Elimine un carácter":"Elimine "+t+" caracteres"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t===1?"Engada un carácter":"Engada "+t+" caracteres"},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){return e.maximum===1?"Só pode seleccionar un elemento":"Só pode seleccionar "+e.maximum+" elementos"},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/he.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="נא למחוק ";return t===1?n+="תו אחד":n+=t+" תווים",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="נא להכניס ";return t===1?n+="תו אחד":n+=t+" תווים",n+=" או יותר",n},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(e){var t="באפשרותך לבחור עד ";return e.maximum===1?t+="פריט אחד":t+=e.maximum+" פריטים",t},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/hi.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/hr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/hsb.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hsb",[],function(){var e=["znamješko","znamješce","znamješka","znamješkow"],t=["zapisk","zapiskaj","zapiski","zapiskow"],n=function(t,n){if(t===1)return n[0];if(t===2)return n[1];if(t>2&&t<=4)return n[2];if(t>=5)return n[3]};return{errorLoading:function(){return"Wuslědki njedachu so začitać."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Prošu zhašej "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Prošu zapodaj znajmjeńša "+r+" "+n(r,e)},loadingMore:function(){return"Dalše wuslědki so začitaja…"},maximumSelected:function(e){return"Móžeš jenož "+e.maximum+" "+n(e.maximum,t)+"wubrać"},noResults:function(){return"Žane wuslědki namakane"},searching:function(){return"Pyta so…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/hu.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{errorLoading:function(){return"Az eredmények betöltése nem sikerült."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/hy.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hy",[],function(){return{errorLoading:function(){return"Արդյունքները հնարավոր չէ բեռնել։"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Խնդրում ենք հեռացնել "+t+" նշան";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Խնդրում ենք մուտքագրել "+t+" կամ ավել նշաններ";return n},loadingMore:function(){return"Բեռնվում են նոր արդյունքներ․․․"},maximumSelected:function(e){var t="Դուք կարող եք ընտրել առավելագույնը "+e.maximum+" կետ";return t},noResults:function(){return"Արդյունքներ չեն գտնվել"},searching:function(){return"Որոնում․․․"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/id.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/is.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/it.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ja.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" 文字を削除してください";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="少なくとも "+t+" 文字を入力してください";return n},loadingMore:function(){return"読み込み中…"},maximumSelected:function(e){var t=e.maximum+" 件しか選択できません";return t},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/km.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="សូមលុបចេញ  "+t+" អក្សរ";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="សូមបញ្ចូល"+t+" អក្សរ រឺ ច្រើនជាងនេះ";return n},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(e){var t="អ្នកអាចជ្រើសរើសបានតែ "+e.maximum+" ជម្រើសប៉ុណ្ណោះ";return t},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ko.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/lt.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%10===1&&(e%100<11||e%100>19)?t:e%10>=2&&e%10<=9&&(e%100<11||e%100>19)?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"į","ius","ių"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"į","ius","ių"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ą","us","ų"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/lv.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par  "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/mk.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ms.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Sila hapuskan "+t+" aksara"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Sila masukkan "+t+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(e){return"Anda hanya boleh memilih "+e.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/nb.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn "+t+" tegn til";return n+" tegn til"},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/nl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t=e.maximum==1?"kan":"kunnen",n="Er "+t+" maar "+e.maximum+" item";return e.maximum!=1&&(n+="s"),n+=" worden geselecteerd",n},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/pl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maximum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ps.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ps",[],function(){return{errorLoading:function(){return"پايلي نه سي ترلاسه کېدای"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="د مهربانۍ لمخي "+t+" توری ړنګ کړئ";return t!=1&&(n=n.replace("توری","توري")),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لږ تر لږه "+t+" يا ډېر توري وليکئ";return n},loadingMore:function(){return"نوري پايلي ترلاسه کيږي..."},maximumSelected:function(e){var t="تاسو يوازي "+e.maximum+" قلم په نښه کولای سی";return e.maximum!=1&&(t=t.replace("قلم","قلمونه")),t},noResults:function(){return"پايلي و نه موندل سوې"},searching:function(){return"لټول کيږي..."}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/pt-BR.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/pt.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"caractere",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ro.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return t!==1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vă rugăm să introduceți "+t+" sau mai multe caractere";return n},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",e.maximum!==1&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/ru.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ru",[],function(){function e(e,t,n,r){return e%10<5&&e%10>0&&e%100<5||e%100>20?e%10>1?n:t:r}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Пожалуйста, введите на "+n+" символ";return r+=e(n,"","a","ов"),r+=" меньше",r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Пожалуйста, введите еще хотя бы "+n+" символ";return r+=e(n,"","a","ов"),r},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(t){var n="Вы можете выбрать не более "+t.maximum+" элемент";return n+=e(t.maximum,"","a","ов"),n},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/sk.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{errorLoading:function(){return"Výsledky sa nepodarilo načítať."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadajte o jeden znak menej":n>=2&&n<=4?"Prosím, zadajte o "+e[n](!0)+" znaky menej":"Prosím, zadajte o "+n+" znakov menej"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadajte ešte jeden znak":n<=4?"Prosím, zadajte ešte ďalšie "+e[n](!0)+" znaky":"Prosím, zadajte ešte ďalších "+n+" znakov"},loadingMore:function(){return"Načítanie ďalších výsledkov…"},maximumSelected:function(t){return t.maximum==1?"Môžete zvoliť len jednu položku":t.maximum>=2&&t.maximum<=4?"Môžete zvoliť najviac "+e[t.maximum](!1)+" položky":"Môžete zvoliť najviac "+t.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/sl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sl",[],function(){return{errorLoading:function(){return"Zadetkov iskanja ni bilo mogoče naložiti."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Prosim zbrišite "+t+" znak";return t==2?n+="a":t!=1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Prosim vpišite še "+t+" znak";return t==2?n+="a":t!=1&&(n+="e"),n},loadingMore:function(){return"Nalagam več zadetkov…"},maximumSelected:function(e){var t="Označite lahko največ "+e.maximum+" predmet";return e.maximum==2?t+="a":e.maximum!=1&&(t+="e"),t},noResults:function(){return"Ni zadetkov."},searching:function(){return"Iščem…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/sr-Cyrl.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr-Cyrl",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Обришите "+n+" симбол";return r+=e(n,"","а","а"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Укуцајте бар још "+n+" симбол";return r+=e(n,"","а","а"),r},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(t){var n="Можете изабрати само "+t.maximum+" ставк";return n+=e(t.maximum,"у","е","и"),n},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/sr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/sv.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vänligen sudda ut "+t+" tecken";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vänligen skriv in "+t+" eller fler tecken";return n},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(e){var t="Du kan max välja "+e.maximum+" element";return t},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/th.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/th",[],function(){return{errorLoading:function(){return"ไม่สามารถค้นข้อมูลได้"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="โปรดลบออก "+t+" ตัวอักษร";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="โปรดพิมพ์เพิ่มอีก "+t+" ตัวอักษร";return n},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(e){var t="คุณสามารถเลือกได้ไม่เกิน "+e.maximum+" รายการ";return t},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/tr.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/tr",[],function(){return{errorLoading:function(){return"Sonuç yüklenemedi"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" karakter daha girmelisiniz";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="En az "+t+" karakter daha girmelisiniz";return n},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(e){var t="Sadece "+e.maximum+" seçim yapabilirsiniz";return t},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/uk.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/uk",[],function(){function e(e,t,n,r){return e%100>10&&e%100<15?r:e%10===1?t:e%10>1&&e%10<5?n:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Будь ласка, видаліть "+n+" "+e(t.maximum,"літеру","літери","літер")},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Будь ласка, введіть "+t+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(t){return"Ви можете вибрати лише "+t.maximum+" "+e(t.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/vi.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/vi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vui lòng nhập ít hơn "+t+" ký tự";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vui lòng nhập nhiều hơn "+t+" ký tự";return n},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(e){var t="Chỉ có thể chọn được "+e.maximum+" lựa chọn";return t},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/zh-CN.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="请删除"+t+"个字符";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="请再输入至少"+t+"个字符";return n},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(e){var t="最多只能选择"+e.maximum+"个项目";return t},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"}}}),{define:e.define,require:e.require}})();

+ 3 - 0
src/web/res/bower_components/select2/dist/js/i18n/zh-TW.js

@@ -0,0 +1,3 @@
+/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
+
+(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="請刪掉"+t+"個字元";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="請再輸入"+t+"個字元";return n},loadingMore:function(){return"載入中…"},maximumSelected:function(e){var t="你只能選擇最多"+e.maximum+"項";return t},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"}}}),{define:e.define,require:e.require}})();

+ 6457 - 0
src/web/res/bower_components/select2/dist/js/select2.full.js

@@ -0,0 +1,6457 @@
+/*!
+ * Select2 4.0.5
+ * https://select2.github.io
+ *
+ * Released under the MIT license
+ * https://github.com/select2/select2/blob/master/LICENSE.md
+ */
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(['jquery'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    // Node/CommonJS
+    module.exports = function (root, jQuery) {
+      if (jQuery === undefined) {
+        // require('jQuery') returns a factory that requires window to
+        // build a jQuery instance, we normalize how we use modules
+        // that require this pattern but the window provided is a noop
+        // if it's defined (how jquery works)
+        if (typeof window !== 'undefined') {
+          jQuery = require('jquery');
+        }
+        else {
+          jQuery = require('jquery')(root);
+        }
+      }
+      factory(jQuery);
+      return jQuery;
+    };
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+} (function (jQuery) {
+  // This is needed so we can catch the AMD loader configuration and use it
+  // The inner file should be wrapped (by `banner.start.js`) in a function that
+  // returns the AMD loader references.
+  var S2 =(function () {
+  // Restore the Select2 AMD loader so it can be used
+  // Needed mostly in the language files, where the loader is not inserted
+  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
+    var S2 = jQuery.fn.select2.amd;
+  }
+var S2;(function () { if (!S2 || !S2.requirejs) {
+if (!S2) { S2 = {}; } else { require = S2; }
+/**
+ * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
+ * Released under MIT license, http://github.com/requirejs/almond/LICENSE
+ */
+//Going sloppy to avoid 'use strict' string cost, but strict practices should
+//be followed.
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+    var main, req, makeMap, handlers,
+        defined = {},
+        waiting = {},
+        config = {},
+        defining = {},
+        hasOwn = Object.prototype.hasOwnProperty,
+        aps = [].slice,
+        jsSuffixRegExp = /\.js$/;
+
+    function hasProp(obj, prop) {
+        return hasOwn.call(obj, prop);
+    }
+
+    /**
+     * Given a relative module name, like ./something, normalize it to
+     * a real name that can be mapped to a path.
+     * @param {String} name the relative name
+     * @param {String} baseName a real name that the name arg is relative
+     * to.
+     * @returns {String} normalized name
+     */
+    function normalize(name, baseName) {
+        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
+            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
+            baseParts = baseName && baseName.split("/"),
+            map = config.map,
+            starMap = (map && map['*']) || {};
+
+        //Adjust any relative paths.
+        if (name) {
+            name = name.split('/');
+            lastIndex = name.length - 1;
+
+            // If wanting node ID compatibility, strip .js from end
+            // of IDs. Have to do this here, and not in nameToUrl
+            // because node allows either .js or non .js to map
+            // to same file.
+            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
+                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
+            }
+
+            // Starts with a '.' so need the baseName
+            if (name[0].charAt(0) === '.' && baseParts) {
+                //Convert baseName to array, and lop off the last part,
+                //so that . matches that 'directory' and not name of the baseName's
+                //module. For instance, baseName of 'one/two/three', maps to
+                //'one/two/three.js', but we want the directory, 'one/two' for
+                //this normalization.
+                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+                name = normalizedBaseParts.concat(name);
+            }
+
+            //start trimDots
+            for (i = 0; i < name.length; i++) {
+                part = name[i];
+                if (part === '.') {
+                    name.splice(i, 1);
+                    i -= 1;
+                } else if (part === '..') {
+                    // If at the start, or previous value is still ..,
+                    // keep them so that when converted to a path it may
+                    // still work when converted to a path, even though
+                    // as an ID it is less than ideal. In larger point
+                    // releases, may be better to just kick out an error.
+                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
+                        continue;
+                    } else if (i > 0) {
+                        name.splice(i - 1, 2);
+                        i -= 2;
+                    }
+                }
+            }
+            //end trimDots
+
+            name = name.join('/');
+        }
+
+        //Apply map config if available.
+        if ((baseParts || starMap) && map) {
+            nameParts = name.split('/');
+
+            for (i = nameParts.length; i > 0; i -= 1) {
+                nameSegment = nameParts.slice(0, i).join("/");
+
+                if (baseParts) {
+                    //Find the longest baseName segment match in the config.
+                    //So, do joins on the biggest to smallest lengths of baseParts.
+                    for (j = baseParts.length; j > 0; j -= 1) {
+                        mapValue = map[baseParts.slice(0, j).join('/')];
+
+                        //baseName segment has  config, find if it has one for
+                        //this name.
+                        if (mapValue) {
+                            mapValue = mapValue[nameSegment];
+                            if (mapValue) {
+                                //Match, update name to the new value.
+                                foundMap = mapValue;
+                                foundI = i;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (foundMap) {
+                    break;
+                }
+
+                //Check for a star map match, but just hold on to it,
+                //if there is a shorter segment match later in a matching
+                //config, then favor over this star map.
+                if (!foundStarMap && starMap && starMap[nameSegment]) {
+                    foundStarMap = starMap[nameSegment];
+                    starI = i;
+                }
+            }
+
+            if (!foundMap && foundStarMap) {
+                foundMap = foundStarMap;
+                foundI = starI;
+            }
+
+            if (foundMap) {
+                nameParts.splice(0, foundI, foundMap);
+                name = nameParts.join('/');
+            }
+        }
+
+        return name;
+    }
+
+    function makeRequire(relName, forceSync) {
+        return function () {
+            //A version of a require function that passes a moduleName
+            //value for items that may need to
+            //look up paths relative to the moduleName
+            var args = aps.call(arguments, 0);
+
+            //If first arg is not require('string'), and there is only
+            //one arg, it is the array form without a callback. Insert
+            //a null so that the following concat is correct.
+            if (typeof args[0] !== 'string' && args.length === 1) {
+                args.push(null);
+            }
+            return req.apply(undef, args.concat([relName, forceSync]));
+        };
+    }
+
+    function makeNormalize(relName) {
+        return function (name) {
+            return normalize(name, relName);
+        };
+    }
+
+    function makeLoad(depName) {
+        return function (value) {
+            defined[depName] = value;
+        };
+    }
+
+    function callDep(name) {
+        if (hasProp(waiting, name)) {
+            var args = waiting[name];
+            delete waiting[name];
+            defining[name] = true;
+            main.apply(undef, args);
+        }
+
+        if (!hasProp(defined, name) && !hasProp(defining, name)) {
+            throw new Error('No ' + name);
+        }
+        return defined[name];
+    }
+
+    //Turns a plugin!resource to [plugin, resource]
+    //with the plugin being undefined if the name
+    //did not have a plugin prefix.
+    function splitPrefix(name) {
+        var prefix,
+            index = name ? name.indexOf('!') : -1;
+        if (index > -1) {
+            prefix = name.substring(0, index);
+            name = name.substring(index + 1, name.length);
+        }
+        return [prefix, name];
+    }
+
+    //Creates a parts array for a relName where first part is plugin ID,
+    //second part is resource ID. Assumes relName has already been normalized.
+    function makeRelParts(relName) {
+        return relName ? splitPrefix(relName) : [];
+    }
+
+    /**
+     * Makes a name map, normalizing the name, and using a plugin
+     * for normalization if necessary. Grabs a ref to plugin
+     * too, as an optimization.
+     */
+    makeMap = function (name, relParts) {
+        var plugin,
+            parts = splitPrefix(name),
+            prefix = parts[0],
+            relResourceName = relParts[1];
+
+        name = parts[1];
+
+        if (prefix) {
+            prefix = normalize(prefix, relResourceName);
+            plugin = callDep(prefix);
+        }
+
+        //Normalize according
+        if (prefix) {
+            if (plugin && plugin.normalize) {
+                name = plugin.normalize(name, makeNormalize(relResourceName));
+            } else {
+                name = normalize(name, relResourceName);
+            }
+        } else {
+            name = normalize(name, relResourceName);
+            parts = splitPrefix(name);
+            prefix = parts[0];
+            name = parts[1];
+            if (prefix) {
+                plugin = callDep(prefix);
+            }
+        }
+
+        //Using ridiculous property names for space reasons
+        return {
+            f: prefix ? prefix + '!' + name : name, //fullName
+            n: name,
+            pr: prefix,
+            p: plugin
+        };
+    };
+
+    function makeConfig(name) {
+        return function () {
+            return (config && config.config && config.config[name]) || {};
+        };
+    }
+
+    handlers = {
+        require: function (name) {
+            return makeRequire(name);
+        },
+        exports: function (name) {
+            var e = defined[name];
+            if (typeof e !== 'undefined') {
+                return e;
+            } else {
+                return (defined[name] = {});
+            }
+        },
+        module: function (name) {
+            return {
+                id: name,
+                uri: '',
+                exports: defined[name],
+                config: makeConfig(name)
+            };
+        }
+    };
+
+    main = function (name, deps, callback, relName) {
+        var cjsModule, depName, ret, map, i, relParts,
+            args = [],
+            callbackType = typeof callback,
+            usingExports;
+
+        //Use name if no relName
+        relName = relName || name;
+        relParts = makeRelParts(relName);
+
+        //Call the callback to define the module, if necessary.
+        if (callbackType === 'undefined' || callbackType === 'function') {
+            //Pull out the defined dependencies and pass the ordered
+            //values to the callback.
+            //Default to [require, exports, module] if no deps
+            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
+            for (i = 0; i < deps.length; i += 1) {
+                map = makeMap(deps[i], relParts);
+                depName = map.f;
+
+                //Fast path CommonJS standard dependencies.
+                if (depName === "require") {
+                    args[i] = handlers.require(name);
+                } else if (depName === "exports") {
+                    //CommonJS module spec 1.1
+                    args[i] = handlers.exports(name);
+                    usingExports = true;
+                } else if (depName === "module") {
+                    //CommonJS module spec 1.1
+                    cjsModule = args[i] = handlers.module(name);
+                } else if (hasProp(defined, depName) ||
+                           hasProp(waiting, depName) ||
+                           hasProp(defining, depName)) {
+                    args[i] = callDep(depName);
+                } else if (map.p) {
+                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+                    args[i] = defined[depName];
+                } else {
+                    throw new Error(name + ' missing ' + depName);
+                }
+            }
+
+            ret = callback ? callback.apply(defined[name], args) : undefined;
+
+            if (name) {
+                //If setting exports via "module" is in play,
+                //favor that over return value and exports. After that,
+                //favor a non-undefined return value over exports use.
+                if (cjsModule && cjsModule.exports !== undef &&
+                        cjsModule.exports !== defined[name]) {
+                    defined[name] = cjsModule.exports;
+                } else if (ret !== undef || !usingExports) {
+                    //Use the return value from the function.
+                    defined[name] = ret;
+                }
+            }
+        } else if (name) {
+            //May just be an object definition for the module. Only
+            //worry about defining if have a module name.
+            defined[name] = callback;
+        }
+    };
+
+    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
+        if (typeof deps === "string") {
+            if (handlers[deps]) {
+                //callback in this case is really relName
+                return handlers[deps](callback);
+            }
+            //Just return the module wanted. In this scenario, the
+            //deps arg is the module name, and second arg (if passed)
+            //is just the relName.
+            //Normalize module name, if it contains . or ..
+            return callDep(makeMap(deps, makeRelParts(callback)).f);
+        } else if (!deps.splice) {
+            //deps is a config object, not an array.
+            config = deps;
+            if (config.deps) {
+                req(config.deps, config.callback);
+            }
+            if (!callback) {
+                return;
+            }
+
+            if (callback.splice) {
+                //callback is an array, which means it is a dependency list.
+                //Adjust args if there are dependencies
+                deps = callback;
+                callback = relName;
+                relName = null;
+            } else {
+                deps = undef;
+            }
+        }
+
+        //Support require(['a'])
+        callback = callback || function () {};
+
+        //If relName is a function, it is an errback handler,
+        //so remove it.
+        if (typeof relName === 'function') {
+            relName = forceSync;
+            forceSync = alt;
+        }
+
+        //Simulate async callback;
+        if (forceSync) {
+            main(undef, deps, callback, relName);
+        } else {
+            //Using a non-zero value because of concern for what old browsers
+            //do, and latest browsers "upgrade" to 4 if lower value is used:
+            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
+            //If want a value immediately, use require('id') instead -- something
+            //that works in almond on the global level, but not guaranteed and
+            //unlikely to work in other AMD implementations.
+            setTimeout(function () {
+                main(undef, deps, callback, relName);
+            }, 4);
+        }
+
+        return req;
+    };
+
+    /**
+     * Just drops the config on the floor, but returns req in case
+     * the config return value is used.
+     */
+    req.config = function (cfg) {
+        return req(cfg);
+    };
+
+    /**
+     * Expose module registry for debugging and tooling
+     */
+    requirejs._defined = defined;
+
+    define = function (name, deps, callback) {
+        if (typeof name !== 'string') {
+            throw new Error('See almond README: incorrect module build, no module name');
+        }
+
+        //This module may not have dependencies
+        if (!deps.splice) {
+            //deps is not an array, so probably means
+            //an object literal or factory function for
+            //the value. Adjust args.
+            callback = deps;
+            deps = [];
+        }
+
+        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
+            waiting[name] = [name, deps, callback];
+        }
+    };
+
+    define.amd = {
+        jQuery: true
+    };
+}());
+
+S2.requirejs = requirejs;S2.require = require;S2.define = define;
+}
+}());
+S2.define("almond", function(){});
+
+/* global jQuery:false, $:false */
+S2.define('jquery',[],function () {
+  var _$ = jQuery || $;
+
+  if (_$ == null && console && console.error) {
+    console.error(
+      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
+      'found. Make sure that you are including jQuery before Select2 on your ' +
+      'web page.'
+    );
+  }
+
+  return _$;
+});
+
+S2.define('select2/utils',[
+  'jquery'
+], function ($) {
+  var Utils = {};
+
+  Utils.Extend = function (ChildClass, SuperClass) {
+    var __hasProp = {}.hasOwnProperty;
+
+    function BaseConstructor () {
+      this.constructor = ChildClass;
+    }
+
+    for (var key in SuperClass) {
+      if (__hasProp.call(SuperClass, key)) {
+        ChildClass[key] = SuperClass[key];
+      }
+    }
+
+    BaseConstructor.prototype = SuperClass.prototype;
+    ChildClass.prototype = new BaseConstructor();
+    ChildClass.__super__ = SuperClass.prototype;
+
+    return ChildClass;
+  };
+
+  function getMethods (theClass) {
+    var proto = theClass.prototype;
+
+    var methods = [];
+
+    for (var methodName in proto) {
+      var m = proto[methodName];
+
+      if (typeof m !== 'function') {
+        continue;
+      }
+
+      if (methodName === 'constructor') {
+        continue;
+      }
+
+      methods.push(methodName);
+    }
+
+    return methods;
+  }
+
+  Utils.Decorate = function (SuperClass, DecoratorClass) {
+    var decoratedMethods = getMethods(DecoratorClass);
+    var superMethods = getMethods(SuperClass);
+
+    function DecoratedClass () {
+      var unshift = Array.prototype.unshift;
+
+      var argCount = DecoratorClass.prototype.constructor.length;
+
+      var calledConstructor = SuperClass.prototype.constructor;
+
+      if (argCount > 0) {
+        unshift.call(arguments, SuperClass.prototype.constructor);
+
+        calledConstructor = DecoratorClass.prototype.constructor;
+      }
+
+      calledConstructor.apply(this, arguments);
+    }
+
+    DecoratorClass.displayName = SuperClass.displayName;
+
+    function ctr () {
+      this.constructor = DecoratedClass;
+    }
+
+    DecoratedClass.prototype = new ctr();
+
+    for (var m = 0; m < superMethods.length; m++) {
+        var superMethod = superMethods[m];
+
+        DecoratedClass.prototype[superMethod] =
+          SuperClass.prototype[superMethod];
+    }
+
+    var calledMethod = function (methodName) {
+      // Stub out the original method if it's not decorating an actual method
+      var originalMethod = function () {};
+
+      if (methodName in DecoratedClass.prototype) {
+        originalMethod = DecoratedClass.prototype[methodName];
+      }
+
+      var decoratedMethod = DecoratorClass.prototype[methodName];
+
+      return function () {
+        var unshift = Array.prototype.unshift;
+
+        unshift.call(arguments, originalMethod);
+
+        return decoratedMethod.apply(this, arguments);
+      };
+    };
+
+    for (var d = 0; d < decoratedMethods.length; d++) {
+      var decoratedMethod = decoratedMethods[d];
+
+      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
+    }
+
+    return DecoratedClass;
+  };
+
+  var Observable = function () {
+    this.listeners = {};
+  };
+
+  Observable.prototype.on = function (event, callback) {
+    this.listeners = this.listeners || {};
+
+    if (event in this.listeners) {
+      this.listeners[event].push(callback);
+    } else {
+      this.listeners[event] = [callback];
+    }
+  };
+
+  Observable.prototype.trigger = function (event) {
+    var slice = Array.prototype.slice;
+    var params = slice.call(arguments, 1);
+
+    this.listeners = this.listeners || {};
+
+    // Params should always come in as an array
+    if (params == null) {
+      params = [];
+    }
+
+    // If there are no arguments to the event, use a temporary object
+    if (params.length === 0) {
+      params.push({});
+    }
+
+    // Set the `_type` of the first object to the event
+    params[0]._type = event;
+
+    if (event in this.listeners) {
+      this.invoke(this.listeners[event], slice.call(arguments, 1));
+    }
+
+    if ('*' in this.listeners) {
+      this.invoke(this.listeners['*'], arguments);
+    }
+  };
+
+  Observable.prototype.invoke = function (listeners, params) {
+    for (var i = 0, len = listeners.length; i < len; i++) {
+      listeners[i].apply(this, params);
+    }
+  };
+
+  Utils.Observable = Observable;
+
+  Utils.generateChars = function (length) {
+    var chars = '';
+
+    for (var i = 0; i < length; i++) {
+      var randomChar = Math.floor(Math.random() * 36);
+      chars += randomChar.toString(36);
+    }
+
+    return chars;
+  };
+
+  Utils.bind = function (func, context) {
+    return function () {
+      func.apply(context, arguments);
+    };
+  };
+
+  Utils._convertData = function (data) {
+    for (var originalKey in data) {
+      var keys = originalKey.split('-');
+
+      var dataLevel = data;
+
+      if (keys.length === 1) {
+        continue;
+      }
+
+      for (var k = 0; k < keys.length; k++) {
+        var key = keys[k];
+
+        // Lowercase the first letter
+        // By default, dash-separated becomes camelCase
+        key = key.substring(0, 1).toLowerCase() + key.substring(1);
+
+        if (!(key in dataLevel)) {
+          dataLevel[key] = {};
+        }
+
+        if (k == keys.length - 1) {
+          dataLevel[key] = data[originalKey];
+        }
+
+        dataLevel = dataLevel[key];
+      }
+
+      delete data[originalKey];
+    }
+
+    return data;
+  };
+
+  Utils.hasScroll = function (index, el) {
+    // Adapted from the function created by @ShadowScripter
+    // and adapted by @BillBarry on the Stack Exchange Code Review website.
+    // The original code can be found at
+    // http://codereview.stackexchange.com/q/13338
+    // and was designed to be used with the Sizzle selector engine.
+
+    var $el = $(el);
+    var overflowX = el.style.overflowX;
+    var overflowY = el.style.overflowY;
+
+    //Check both x and y declarations
+    if (overflowX === overflowY &&
+        (overflowY === 'hidden' || overflowY === 'visible')) {
+      return false;
+    }
+
+    if (overflowX === 'scroll' || overflowY === 'scroll') {
+      return true;
+    }
+
+    return ($el.innerHeight() < el.scrollHeight ||
+      $el.innerWidth() < el.scrollWidth);
+  };
+
+  Utils.escapeMarkup = function (markup) {
+    var replaceMap = {
+      '\\': '&#92;',
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      '\'': '&#39;',
+      '/': '&#47;'
+    };
+
+    // Do not try to escape the markup if it's not a string
+    if (typeof markup !== 'string') {
+      return markup;
+    }
+
+    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
+      return replaceMap[match];
+    });
+  };
+
+  // Append an array of jQuery nodes to a given element.
+  Utils.appendMany = function ($element, $nodes) {
+    // jQuery 1.7.x does not support $.fn.append() with an array
+    // Fall back to a jQuery object collection using $.fn.add()
+    if ($.fn.jquery.substr(0, 3) === '1.7') {
+      var $jqNodes = $();
+
+      $.map($nodes, function (node) {
+        $jqNodes = $jqNodes.add(node);
+      });
+
+      $nodes = $jqNodes;
+    }
+
+    $element.append($nodes);
+  };
+
+  return Utils;
+});
+
+S2.define('select2/results',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Results ($element, options, dataAdapter) {
+    this.$element = $element;
+    this.data = dataAdapter;
+    this.options = options;
+
+    Results.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Results, Utils.Observable);
+
+  Results.prototype.render = function () {
+    var $results = $(
+      '<ul class="select2-results__options" role="tree"></ul>'
+    );
+
+    if (this.options.get('multiple')) {
+      $results.attr('aria-multiselectable', 'true');
+    }
+
+    this.$results = $results;
+
+    return $results;
+  };
+
+  Results.prototype.clear = function () {
+    this.$results.empty();
+  };
+
+  Results.prototype.displayMessage = function (params) {
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    this.clear();
+    this.hideLoading();
+
+    var $message = $(
+      '<li role="treeitem" aria-live="assertive"' +
+      ' class="select2-results__option"></li>'
+    );
+
+    var message = this.options.get('translations').get(params.message);
+
+    $message.append(
+      escapeMarkup(
+        message(params.args)
+      )
+    );
+
+    $message[0].className += ' select2-results__message';
+
+    this.$results.append($message);
+  };
+
+  Results.prototype.hideMessages = function () {
+    this.$results.find('.select2-results__message').remove();
+  };
+
+  Results.prototype.append = function (data) {
+    this.hideLoading();
+
+    var $options = [];
+
+    if (data.results == null || data.results.length === 0) {
+      if (this.$results.children().length === 0) {
+        this.trigger('results:message', {
+          message: 'noResults'
+        });
+      }
+
+      return;
+    }
+
+    data.results = this.sort(data.results);
+
+    for (var d = 0; d < data.results.length; d++) {
+      var item = data.results[d];
+
+      var $option = this.option(item);
+
+      $options.push($option);
+    }
+
+    this.$results.append($options);
+  };
+
+  Results.prototype.position = function ($results, $dropdown) {
+    var $resultsContainer = $dropdown.find('.select2-results');
+    $resultsContainer.append($results);
+  };
+
+  Results.prototype.sort = function (data) {
+    var sorter = this.options.get('sorter');
+
+    return sorter(data);
+  };
+
+  Results.prototype.highlightFirstItem = function () {
+    var $options = this.$results
+      .find('.select2-results__option[aria-selected]');
+
+    var $selected = $options.filter('[aria-selected=true]');
+
+    // Check if there are any selected options
+    if ($selected.length > 0) {
+      // If there are selected options, highlight the first
+      $selected.first().trigger('mouseenter');
+    } else {
+      // If there are no selected options, highlight the first option
+      // in the dropdown
+      $options.first().trigger('mouseenter');
+    }
+
+    this.ensureHighlightVisible();
+  };
+
+  Results.prototype.setClasses = function () {
+    var self = this;
+
+    this.data.current(function (selected) {
+      var selectedIds = $.map(selected, function (s) {
+        return s.id.toString();
+      });
+
+      var $options = self.$results
+        .find('.select2-results__option[aria-selected]');
+
+      $options.each(function () {
+        var $option = $(this);
+
+        var item = $.data(this, 'data');
+
+        // id needs to be converted to a string when comparing
+        var id = '' + item.id;
+
+        if ((item.element != null && item.element.selected) ||
+            (item.element == null && $.inArray(id, selectedIds) > -1)) {
+          $option.attr('aria-selected', 'true');
+        } else {
+          $option.attr('aria-selected', 'false');
+        }
+      });
+
+    });
+  };
+
+  Results.prototype.showLoading = function (params) {
+    this.hideLoading();
+
+    var loadingMore = this.options.get('translations').get('searching');
+
+    var loading = {
+      disabled: true,
+      loading: true,
+      text: loadingMore(params)
+    };
+    var $loading = this.option(loading);
+    $loading.className += ' loading-results';
+
+    this.$results.prepend($loading);
+  };
+
+  Results.prototype.hideLoading = function () {
+    this.$results.find('.loading-results').remove();
+  };
+
+  Results.prototype.option = function (data) {
+    var option = document.createElement('li');
+    option.className = 'select2-results__option';
+
+    var attrs = {
+      'role': 'treeitem',
+      'aria-selected': 'false'
+    };
+
+    if (data.disabled) {
+      delete attrs['aria-selected'];
+      attrs['aria-disabled'] = 'true';
+    }
+
+    if (data.id == null) {
+      delete attrs['aria-selected'];
+    }
+
+    if (data._resultId != null) {
+      option.id = data._resultId;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    if (data.children) {
+      attrs.role = 'group';
+      attrs['aria-label'] = data.text;
+      delete attrs['aria-selected'];
+    }
+
+    for (var attr in attrs) {
+      var val = attrs[attr];
+
+      option.setAttribute(attr, val);
+    }
+
+    if (data.children) {
+      var $option = $(option);
+
+      var label = document.createElement('strong');
+      label.className = 'select2-results__group';
+
+      var $label = $(label);
+      this.template(data, label);
+
+      var $children = [];
+
+      for (var c = 0; c < data.children.length; c++) {
+        var child = data.children[c];
+
+        var $child = this.option(child);
+
+        $children.push($child);
+      }
+
+      var $childrenContainer = $('<ul></ul>', {
+        'class': 'select2-results__options select2-results__options--nested'
+      });
+
+      $childrenContainer.append($children);
+
+      $option.append(label);
+      $option.append($childrenContainer);
+    } else {
+      this.template(data, option);
+    }
+
+    $.data(option, 'data', data);
+
+    return option;
+  };
+
+  Results.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-results';
+
+    this.$results.attr('id', id);
+
+    container.on('results:all', function (params) {
+      self.clear();
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+        self.highlightFirstItem();
+      }
+    });
+
+    container.on('results:append', function (params) {
+      self.append(params.data);
+
+      if (container.isOpen()) {
+        self.setClasses();
+      }
+    });
+
+    container.on('query', function (params) {
+      self.hideMessages();
+      self.showLoading(params);
+    });
+
+    container.on('select', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+      self.highlightFirstItem();
+    });
+
+    container.on('unselect', function () {
+      if (!container.isOpen()) {
+        return;
+      }
+
+      self.setClasses();
+      self.highlightFirstItem();
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expended="true"
+      self.$results.attr('aria-expanded', 'true');
+      self.$results.attr('aria-hidden', 'false');
+
+      self.setClasses();
+      self.ensureHighlightVisible();
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expended="false"
+      self.$results.attr('aria-expanded', 'false');
+      self.$results.attr('aria-hidden', 'true');
+      self.$results.removeAttr('aria-activedescendant');
+    });
+
+    container.on('results:toggle', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      $highlighted.trigger('mouseup');
+    });
+
+    container.on('results:select', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      if ($highlighted.length === 0) {
+        return;
+      }
+
+      var data = $highlighted.data('data');
+
+      if ($highlighted.attr('aria-selected') == 'true') {
+        self.trigger('close', {});
+      } else {
+        self.trigger('select', {
+          data: data
+        });
+      }
+    });
+
+    container.on('results:previous', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      // If we are already at te top, don't move further
+      if (currentIndex === 0) {
+        return;
+      }
+
+      var nextIndex = currentIndex - 1;
+
+      // If none are highlighted, highlight the first
+      if ($highlighted.length === 0) {
+        nextIndex = 0;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top;
+      var nextTop = $next.offset().top;
+      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextTop - currentOffset < 0) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:next', function () {
+      var $highlighted = self.getHighlightedResults();
+
+      var $options = self.$results.find('[aria-selected]');
+
+      var currentIndex = $options.index($highlighted);
+
+      var nextIndex = currentIndex + 1;
+
+      // If we are at the last option, stay there
+      if (nextIndex >= $options.length) {
+        return;
+      }
+
+      var $next = $options.eq(nextIndex);
+
+      $next.trigger('mouseenter');
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var nextBottom = $next.offset().top + $next.outerHeight(false);
+      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
+
+      if (nextIndex === 0) {
+        self.$results.scrollTop(0);
+      } else if (nextBottom > currentOffset) {
+        self.$results.scrollTop(nextOffset);
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      params.element.addClass('select2-results__option--highlighted');
+    });
+
+    container.on('results:message', function (params) {
+      self.displayMessage(params);
+    });
+
+    if ($.fn.mousewheel) {
+      this.$results.on('mousewheel', function (e) {
+        var top = self.$results.scrollTop();
+
+        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
+
+        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
+        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
+
+        if (isAtTop) {
+          self.$results.scrollTop(0);
+
+          e.preventDefault();
+          e.stopPropagation();
+        } else if (isAtBottom) {
+          self.$results.scrollTop(
+            self.$results.get(0).scrollHeight - self.$results.height()
+          );
+
+          e.preventDefault();
+          e.stopPropagation();
+        }
+      });
+    }
+
+    this.$results.on('mouseup', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var $this = $(this);
+
+      var data = $this.data('data');
+
+      if ($this.attr('aria-selected') === 'true') {
+        if (self.options.get('multiple')) {
+          self.trigger('unselect', {
+            originalEvent: evt,
+            data: data
+          });
+        } else {
+          self.trigger('close', {});
+        }
+
+        return;
+      }
+
+      self.trigger('select', {
+        originalEvent: evt,
+        data: data
+      });
+    });
+
+    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
+      function (evt) {
+      var data = $(this).data('data');
+
+      self.getHighlightedResults()
+          .removeClass('select2-results__option--highlighted');
+
+      self.trigger('results:focus', {
+        data: data,
+        element: $(this)
+      });
+    });
+  };
+
+  Results.prototype.getHighlightedResults = function () {
+    var $highlighted = this.$results
+    .find('.select2-results__option--highlighted');
+
+    return $highlighted;
+  };
+
+  Results.prototype.destroy = function () {
+    this.$results.remove();
+  };
+
+  Results.prototype.ensureHighlightVisible = function () {
+    var $highlighted = this.getHighlightedResults();
+
+    if ($highlighted.length === 0) {
+      return;
+    }
+
+    var $options = this.$results.find('[aria-selected]');
+
+    var currentIndex = $options.index($highlighted);
+
+    var currentOffset = this.$results.offset().top;
+    var nextTop = $highlighted.offset().top;
+    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
+
+    var offsetDelta = nextTop - currentOffset;
+    nextOffset -= $highlighted.outerHeight(false) * 2;
+
+    if (currentIndex <= 2) {
+      this.$results.scrollTop(0);
+    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
+      this.$results.scrollTop(nextOffset);
+    }
+  };
+
+  Results.prototype.template = function (result, container) {
+    var template = this.options.get('templateResult');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    var content = template(result, container);
+
+    if (content == null) {
+      container.style.display = 'none';
+    } else if (typeof content === 'string') {
+      container.innerHTML = escapeMarkup(content);
+    } else {
+      $(container).append(content);
+    }
+  };
+
+  return Results;
+});
+
+S2.define('select2/keys',[
+
+], function () {
+  var KEYS = {
+    BACKSPACE: 8,
+    TAB: 9,
+    ENTER: 13,
+    SHIFT: 16,
+    CTRL: 17,
+    ALT: 18,
+    ESC: 27,
+    SPACE: 32,
+    PAGE_UP: 33,
+    PAGE_DOWN: 34,
+    END: 35,
+    HOME: 36,
+    LEFT: 37,
+    UP: 38,
+    RIGHT: 39,
+    DOWN: 40,
+    DELETE: 46
+  };
+
+  return KEYS;
+});
+
+S2.define('select2/selection/base',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function BaseSelection ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    BaseSelection.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseSelection, Utils.Observable);
+
+  BaseSelection.prototype.render = function () {
+    var $selection = $(
+      '<span class="select2-selection" role="combobox" ' +
+      ' aria-haspopup="true" aria-expanded="false">' +
+      '</span>'
+    );
+
+    this._tabindex = 0;
+
+    if (this.$element.data('old-tabindex') != null) {
+      this._tabindex = this.$element.data('old-tabindex');
+    } else if (this.$element.attr('tabindex') != null) {
+      this._tabindex = this.$element.attr('tabindex');
+    }
+
+    $selection.attr('title', this.$element.attr('title'));
+    $selection.attr('tabindex', this._tabindex);
+
+    this.$selection = $selection;
+
+    return $selection;
+  };
+
+  BaseSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    var id = container.id + '-container';
+    var resultsId = container.id + '-results';
+
+    this.container = container;
+
+    this.$selection.on('focus', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('blur', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      if (evt.which === KEYS.SPACE) {
+        evt.preventDefault();
+      }
+    });
+
+    container.on('results:focus', function (params) {
+      self.$selection.attr('aria-activedescendant', params.data._resultId);
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+
+    container.on('open', function () {
+      // When the dropdown is open, aria-expanded="true"
+      self.$selection.attr('aria-expanded', 'true');
+      self.$selection.attr('aria-owns', resultsId);
+
+      self._attachCloseHandler(container);
+    });
+
+    container.on('close', function () {
+      // When the dropdown is closed, aria-expanded="false"
+      self.$selection.attr('aria-expanded', 'false');
+      self.$selection.removeAttr('aria-activedescendant');
+      self.$selection.removeAttr('aria-owns');
+
+      self.$selection.focus();
+
+      self._detachCloseHandler(container);
+    });
+
+    container.on('enable', function () {
+      self.$selection.attr('tabindex', self._tabindex);
+    });
+
+    container.on('disable', function () {
+      self.$selection.attr('tabindex', '-1');
+    });
+  };
+
+  BaseSelection.prototype._handleBlur = function (evt) {
+    var self = this;
+
+    // This needs to be delayed as the active element is the body when the tab
+    // key is pressed, possibly along with others.
+    window.setTimeout(function () {
+      // Don't trigger `blur` if the focus is still in the selection
+      if (
+        (document.activeElement == self.$selection[0]) ||
+        ($.contains(self.$selection[0], document.activeElement))
+      ) {
+        return;
+      }
+
+      self.trigger('blur', evt);
+    }, 1);
+  };
+
+  BaseSelection.prototype._attachCloseHandler = function (container) {
+    var self = this;
+
+    $(document.body).on('mousedown.select2.' + container.id, function (e) {
+      var $target = $(e.target);
+
+      var $select = $target.closest('.select2');
+
+      var $all = $('.select2.select2-container--open');
+
+      $all.each(function () {
+        var $this = $(this);
+
+        if (this == $select[0]) {
+          return;
+        }
+
+        var $element = $this.data('element');
+
+        $element.select2('close');
+      });
+    });
+  };
+
+  BaseSelection.prototype._detachCloseHandler = function (container) {
+    $(document.body).off('mousedown.select2.' + container.id);
+  };
+
+  BaseSelection.prototype.position = function ($selection, $container) {
+    var $selectionContainer = $container.find('.selection');
+    $selectionContainer.append($selection);
+  };
+
+  BaseSelection.prototype.destroy = function () {
+    this._detachCloseHandler(this.container);
+  };
+
+  BaseSelection.prototype.update = function (data) {
+    throw new Error('The `update` method must be defined in child classes.');
+  };
+
+  return BaseSelection;
+});
+
+S2.define('select2/selection/single',[
+  'jquery',
+  './base',
+  '../utils',
+  '../keys'
+], function ($, BaseSelection, Utils, KEYS) {
+  function SingleSelection () {
+    SingleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(SingleSelection, BaseSelection);
+
+  SingleSelection.prototype.render = function () {
+    var $selection = SingleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--single');
+
+    $selection.html(
+      '<span class="select2-selection__rendered"></span>' +
+      '<span class="select2-selection__arrow" role="presentation">' +
+        '<b role="presentation"></b>' +
+      '</span>'
+    );
+
+    return $selection;
+  };
+
+  SingleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    SingleSelection.__super__.bind.apply(this, arguments);
+
+    var id = container.id + '-container';
+
+    this.$selection.find('.select2-selection__rendered').attr('id', id);
+    this.$selection.attr('aria-labelledby', id);
+
+    this.$selection.on('mousedown', function (evt) {
+      // Only respond to left clicks
+      if (evt.which !== 1) {
+        return;
+      }
+
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on('focus', function (evt) {
+      // User focuses on the container
+    });
+
+    this.$selection.on('blur', function (evt) {
+      // User exits the container
+    });
+
+    container.on('focus', function (evt) {
+      if (!container.isOpen()) {
+        self.$selection.focus();
+      }
+    });
+
+    container.on('selection:update', function (params) {
+      self.update(params.data);
+    });
+  };
+
+  SingleSelection.prototype.clear = function () {
+    this.$selection.find('.select2-selection__rendered').empty();
+  };
+
+  SingleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  SingleSelection.prototype.selectionContainer = function () {
+    return $('<span></span>');
+  };
+
+  SingleSelection.prototype.update = function (data) {
+    if (data.length === 0) {
+      this.clear();
+      return;
+    }
+
+    var selection = data[0];
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+    var formatted = this.display(selection, $rendered);
+
+    $rendered.empty().append(formatted);
+    $rendered.prop('title', selection.title || selection.text);
+  };
+
+  return SingleSelection;
+});
+
+S2.define('select2/selection/multiple',[
+  'jquery',
+  './base',
+  '../utils'
+], function ($, BaseSelection, Utils) {
+  function MultipleSelection ($element, options) {
+    MultipleSelection.__super__.constructor.apply(this, arguments);
+  }
+
+  Utils.Extend(MultipleSelection, BaseSelection);
+
+  MultipleSelection.prototype.render = function () {
+    var $selection = MultipleSelection.__super__.render.call(this);
+
+    $selection.addClass('select2-selection--multiple');
+
+    $selection.html(
+      '<ul class="select2-selection__rendered"></ul>'
+    );
+
+    return $selection;
+  };
+
+  MultipleSelection.prototype.bind = function (container, $container) {
+    var self = this;
+
+    MultipleSelection.__super__.bind.apply(this, arguments);
+
+    this.$selection.on('click', function (evt) {
+      self.trigger('toggle', {
+        originalEvent: evt
+      });
+    });
+
+    this.$selection.on(
+      'click',
+      '.select2-selection__choice__remove',
+      function (evt) {
+        // Ignore the event if it is disabled
+        if (self.options.get('disabled')) {
+          return;
+        }
+
+        var $remove = $(this);
+        var $selection = $remove.parent();
+
+        var data = $selection.data('data');
+
+        self.trigger('unselect', {
+          originalEvent: evt,
+          data: data
+        });
+      }
+    );
+  };
+
+  MultipleSelection.prototype.clear = function () {
+    this.$selection.find('.select2-selection__rendered').empty();
+  };
+
+  MultipleSelection.prototype.display = function (data, container) {
+    var template = this.options.get('templateSelection');
+    var escapeMarkup = this.options.get('escapeMarkup');
+
+    return escapeMarkup(template(data, container));
+  };
+
+  MultipleSelection.prototype.selectionContainer = function () {
+    var $container = $(
+      '<li class="select2-selection__choice">' +
+        '<span class="select2-selection__choice__remove" role="presentation">' +
+          '&times;' +
+        '</span>' +
+      '</li>'
+    );
+
+    return $container;
+  };
+
+  MultipleSelection.prototype.update = function (data) {
+    this.clear();
+
+    if (data.length === 0) {
+      return;
+    }
+
+    var $selections = [];
+
+    for (var d = 0; d < data.length; d++) {
+      var selection = data[d];
+
+      var $selection = this.selectionContainer();
+      var formatted = this.display(selection, $selection);
+
+      $selection.append(formatted);
+      $selection.prop('title', selection.title || selection.text);
+
+      $selection.data('data', selection);
+
+      $selections.push($selection);
+    }
+
+    var $rendered = this.$selection.find('.select2-selection__rendered');
+
+    Utils.appendMany($rendered, $selections);
+  };
+
+  return MultipleSelection;
+});
+
+S2.define('select2/selection/placeholder',[
+  '../utils'
+], function (Utils) {
+  function Placeholder (decorated, $element, options) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options);
+  }
+
+  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
+    var $placeholder = this.selectionContainer();
+
+    $placeholder.html(this.display(placeholder));
+    $placeholder.addClass('select2-selection__placeholder')
+                .removeClass('select2-selection__choice');
+
+    return $placeholder;
+  };
+
+  Placeholder.prototype.update = function (decorated, data) {
+    var singlePlaceholder = (
+      data.length == 1 && data[0].id != this.placeholder.id
+    );
+    var multipleSelections = data.length > 1;
+
+    if (multipleSelections || singlePlaceholder) {
+      return decorated.call(this, data);
+    }
+
+    this.clear();
+
+    var $placeholder = this.createPlaceholder(this.placeholder);
+
+    this.$selection.find('.select2-selection__rendered').append($placeholder);
+  };
+
+  return Placeholder;
+});
+
+S2.define('select2/selection/allowClear',[
+  'jquery',
+  '../keys'
+], function ($, KEYS) {
+  function AllowClear () { }
+
+  AllowClear.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    if (this.placeholder == null) {
+      if (this.options.get('debug') && window.console && console.error) {
+        console.error(
+          'Select2: The `allowClear` option should be used in combination ' +
+          'with the `placeholder` option.'
+        );
+      }
+    }
+
+    this.$selection.on('mousedown', '.select2-selection__clear',
+      function (evt) {
+        self._handleClear(evt);
+    });
+
+    container.on('keypress', function (evt) {
+      self._handleKeyboardClear(evt, container);
+    });
+  };
+
+  AllowClear.prototype._handleClear = function (_, evt) {
+    // Ignore the event if it is disabled
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    var $clear = this.$selection.find('.select2-selection__clear');
+
+    // Ignore the event if nothing has been selected
+    if ($clear.length === 0) {
+      return;
+    }
+
+    evt.stopPropagation();
+
+    var data = $clear.data('data');
+
+    for (var d = 0; d < data.length; d++) {
+      var unselectData = {
+        data: data[d]
+      };
+
+      // Trigger the `unselect` event, so people can prevent it from being
+      // cleared.
+      this.trigger('unselect', unselectData);
+
+      // If the event was prevented, don't clear it out.
+      if (unselectData.prevented) {
+        return;
+      }
+    }
+
+    this.$element.val(this.placeholder.id).trigger('change');
+
+    this.trigger('toggle', {});
+  };
+
+  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
+    if (container.isOpen()) {
+      return;
+    }
+
+    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
+      this._handleClear(evt);
+    }
+  };
+
+  AllowClear.prototype.update = function (decorated, data) {
+    decorated.call(this, data);
+
+    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
+        data.length === 0) {
+      return;
+    }
+
+    var $remove = $(
+      '<span class="select2-selection__clear">' +
+        '&times;' +
+      '</span>'
+    );
+    $remove.data('data', data);
+
+    this.$selection.find('.select2-selection__rendered').prepend($remove);
+  };
+
+  return AllowClear;
+});
+
+S2.define('select2/selection/search',[
+  'jquery',
+  '../utils',
+  '../keys'
+], function ($, Utils, KEYS) {
+  function Search (decorated, $element, options) {
+    decorated.call(this, $element, options);
+  }
+
+  Search.prototype.render = function (decorated) {
+    var $search = $(
+      '<li class="select2-search select2-search--inline">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
+      '</li>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    var $rendered = decorated.call(this);
+
+    this._transferTabIndex();
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self.$search.trigger('focus');
+    });
+
+    container.on('close', function () {
+      self.$search.val('');
+      self.$search.removeAttr('aria-activedescendant');
+      self.$search.trigger('focus');
+    });
+
+    container.on('enable', function () {
+      self.$search.prop('disabled', false);
+
+      self._transferTabIndex();
+    });
+
+    container.on('disable', function () {
+      self.$search.prop('disabled', true);
+    });
+
+    container.on('focus', function (evt) {
+      self.$search.trigger('focus');
+    });
+
+    container.on('results:focus', function (params) {
+      self.$search.attr('aria-activedescendant', params.id);
+    });
+
+    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
+      self._handleBlur(evt);
+    });
+
+    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
+      evt.stopPropagation();
+
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+
+      var key = evt.which;
+
+      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
+        var $previousChoice = self.$searchContainer
+          .prev('.select2-selection__choice');
+
+        if ($previousChoice.length > 0) {
+          var item = $previousChoice.data('data');
+
+          self.searchRemoveChoice(item);
+
+          evt.preventDefault();
+        }
+      }
+    });
+
+    // Try to detect the IE version should the `documentMode` property that
+    // is stored on the document. This is only implemented in IE and is
+    // slightly cleaner than doing a user agent check.
+    // This property is not available in Edge, but Edge also doesn't have
+    // this bug.
+    var msie = document.documentMode;
+    var disableInputEvents = msie && msie <= 11;
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$selection.on(
+      'input.searchcheck',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents) {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        // Unbind the duplicated `keyup` event
+        self.$selection.off('keyup.search');
+      }
+    );
+
+    this.$selection.on(
+      'keyup.search input.search',
+      '.select2-search--inline',
+      function (evt) {
+        // IE will trigger the `input` event when a placeholder is used on a
+        // search box. To get around this issue, we are forced to ignore all
+        // `input` events in IE and keep using `keyup`.
+        if (disableInputEvents && evt.type === 'input') {
+          self.$selection.off('input.search input.searchcheck');
+          return;
+        }
+
+        var key = evt.which;
+
+        // We can freely ignore events from modifier keys
+        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
+          return;
+        }
+
+        // Tabbing will be handled during the `keydown` phase
+        if (key == KEYS.TAB) {
+          return;
+        }
+
+        self.handleSearch(evt);
+      }
+    );
+  };
+
+  /**
+   * This method will transfer the tabindex attribute from the rendered
+   * selection to the search box. This allows for the search box to be used as
+   * the primary focus instead of the selection container.
+   *
+   * @private
+   */
+  Search.prototype._transferTabIndex = function (decorated) {
+    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
+    this.$selection.attr('tabindex', '-1');
+  };
+
+  Search.prototype.createPlaceholder = function (decorated, placeholder) {
+    this.$search.attr('placeholder', placeholder.text);
+  };
+
+  Search.prototype.update = function (decorated, data) {
+    var searchHadFocus = this.$search[0] == document.activeElement;
+
+    this.$search.attr('placeholder', '');
+
+    decorated.call(this, data);
+
+    this.$selection.find('.select2-selection__rendered')
+                   .append(this.$searchContainer);
+
+    this.resizeSearch();
+    if (searchHadFocus) {
+      this.$search.focus();
+    }
+  };
+
+  Search.prototype.handleSearch = function () {
+    this.resizeSearch();
+
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.searchRemoveChoice = function (decorated, item) {
+    this.trigger('unselect', {
+      data: item
+    });
+
+    this.$search.val(item.text);
+    this.handleSearch();
+  };
+
+  Search.prototype.resizeSearch = function () {
+    this.$search.css('width', '25px');
+
+    var width = '';
+
+    if (this.$search.attr('placeholder') !== '') {
+      width = this.$selection.find('.select2-selection__rendered').innerWidth();
+    } else {
+      var minimumWidth = this.$search.val().length + 1;
+
+      width = (minimumWidth * 0.75) + 'em';
+    }
+
+    this.$search.css('width', width);
+  };
+
+  return Search;
+});
+
+S2.define('select2/selection/eventRelay',[
+  'jquery'
+], function ($) {
+  function EventRelay () { }
+
+  EventRelay.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+    var relayEvents = [
+      'open', 'opening',
+      'close', 'closing',
+      'select', 'selecting',
+      'unselect', 'unselecting'
+    ];
+
+    var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
+
+    decorated.call(this, container, $container);
+
+    container.on('*', function (name, params) {
+      // Ignore events that should not be relayed
+      if ($.inArray(name, relayEvents) === -1) {
+        return;
+      }
+
+      // The parameters should always be an object
+      params = params || {};
+
+      // Generate the jQuery event for the Select2 event
+      var evt = $.Event('select2:' + name, {
+        params: params
+      });
+
+      self.$element.trigger(evt);
+
+      // Only handle preventable events if it was one
+      if ($.inArray(name, preventableEvents) === -1) {
+        return;
+      }
+
+      params.prevented = evt.isDefaultPrevented();
+    });
+  };
+
+  return EventRelay;
+});
+
+S2.define('select2/translation',[
+  'jquery',
+  'require'
+], function ($, require) {
+  function Translation (dict) {
+    this.dict = dict || {};
+  }
+
+  Translation.prototype.all = function () {
+    return this.dict;
+  };
+
+  Translation.prototype.get = function (key) {
+    return this.dict[key];
+  };
+
+  Translation.prototype.extend = function (translation) {
+    this.dict = $.extend({}, translation.all(), this.dict);
+  };
+
+  // Static functions
+
+  Translation._cache = {};
+
+  Translation.loadPath = function (path) {
+    if (!(path in Translation._cache)) {
+      var translations = require(path);
+
+      Translation._cache[path] = translations;
+    }
+
+    return new Translation(Translation._cache[path]);
+  };
+
+  return Translation;
+});
+
+S2.define('select2/diacritics',[
+
+], function () {
+  var diacritics = {
+    '\u24B6': 'A',
+    '\uFF21': 'A',
+    '\u00C0': 'A',
+    '\u00C1': 'A',
+    '\u00C2': 'A',
+    '\u1EA6': 'A',
+    '\u1EA4': 'A',
+    '\u1EAA': 'A',
+    '\u1EA8': 'A',
+    '\u00C3': 'A',
+    '\u0100': 'A',
+    '\u0102': 'A',
+    '\u1EB0': 'A',
+    '\u1EAE': 'A',
+    '\u1EB4': 'A',
+    '\u1EB2': 'A',
+    '\u0226': 'A',
+    '\u01E0': 'A',
+    '\u00C4': 'A',
+    '\u01DE': 'A',
+    '\u1EA2': 'A',
+    '\u00C5': 'A',
+    '\u01FA': 'A',
+    '\u01CD': 'A',
+    '\u0200': 'A',
+    '\u0202': 'A',
+    '\u1EA0': 'A',
+    '\u1EAC': 'A',
+    '\u1EB6': 'A',
+    '\u1E00': 'A',
+    '\u0104': 'A',
+    '\u023A': 'A',
+    '\u2C6F': 'A',
+    '\uA732': 'AA',
+    '\u00C6': 'AE',
+    '\u01FC': 'AE',
+    '\u01E2': 'AE',
+    '\uA734': 'AO',
+    '\uA736': 'AU',
+    '\uA738': 'AV',
+    '\uA73A': 'AV',
+    '\uA73C': 'AY',
+    '\u24B7': 'B',
+    '\uFF22': 'B',
+    '\u1E02': 'B',
+    '\u1E04': 'B',
+    '\u1E06': 'B',
+    '\u0243': 'B',
+    '\u0182': 'B',
+    '\u0181': 'B',
+    '\u24B8': 'C',
+    '\uFF23': 'C',
+    '\u0106': 'C',
+    '\u0108': 'C',
+    '\u010A': 'C',
+    '\u010C': 'C',
+    '\u00C7': 'C',
+    '\u1E08': 'C',
+    '\u0187': 'C',
+    '\u023B': 'C',
+    '\uA73E': 'C',
+    '\u24B9': 'D',
+    '\uFF24': 'D',
+    '\u1E0A': 'D',
+    '\u010E': 'D',
+    '\u1E0C': 'D',
+    '\u1E10': 'D',
+    '\u1E12': 'D',
+    '\u1E0E': 'D',
+    '\u0110': 'D',
+    '\u018B': 'D',
+    '\u018A': 'D',
+    '\u0189': 'D',
+    '\uA779': 'D',
+    '\u01F1': 'DZ',
+    '\u01C4': 'DZ',
+    '\u01F2': 'Dz',
+    '\u01C5': 'Dz',
+    '\u24BA': 'E',
+    '\uFF25': 'E',
+    '\u00C8': 'E',
+    '\u00C9': 'E',
+    '\u00CA': 'E',
+    '\u1EC0': 'E',
+    '\u1EBE': 'E',
+    '\u1EC4': 'E',
+    '\u1EC2': 'E',
+    '\u1EBC': 'E',
+    '\u0112': 'E',
+    '\u1E14': 'E',
+    '\u1E16': 'E',
+    '\u0114': 'E',
+    '\u0116': 'E',
+    '\u00CB': 'E',
+    '\u1EBA': 'E',
+    '\u011A': 'E',
+    '\u0204': 'E',
+    '\u0206': 'E',
+    '\u1EB8': 'E',
+    '\u1EC6': 'E',
+    '\u0228': 'E',
+    '\u1E1C': 'E',
+    '\u0118': 'E',
+    '\u1E18': 'E',
+    '\u1E1A': 'E',
+    '\u0190': 'E',
+    '\u018E': 'E',
+    '\u24BB': 'F',
+    '\uFF26': 'F',
+    '\u1E1E': 'F',
+    '\u0191': 'F',
+    '\uA77B': 'F',
+    '\u24BC': 'G',
+    '\uFF27': 'G',
+    '\u01F4': 'G',
+    '\u011C': 'G',
+    '\u1E20': 'G',
+    '\u011E': 'G',
+    '\u0120': 'G',
+    '\u01E6': 'G',
+    '\u0122': 'G',
+    '\u01E4': 'G',
+    '\u0193': 'G',
+    '\uA7A0': 'G',
+    '\uA77D': 'G',
+    '\uA77E': 'G',
+    '\u24BD': 'H',
+    '\uFF28': 'H',
+    '\u0124': 'H',
+    '\u1E22': 'H',
+    '\u1E26': 'H',
+    '\u021E': 'H',
+    '\u1E24': 'H',
+    '\u1E28': 'H',
+    '\u1E2A': 'H',
+    '\u0126': 'H',
+    '\u2C67': 'H',
+    '\u2C75': 'H',
+    '\uA78D': 'H',
+    '\u24BE': 'I',
+    '\uFF29': 'I',
+    '\u00CC': 'I',
+    '\u00CD': 'I',
+    '\u00CE': 'I',
+    '\u0128': 'I',
+    '\u012A': 'I',
+    '\u012C': 'I',
+    '\u0130': 'I',
+    '\u00CF': 'I',
+    '\u1E2E': 'I',
+    '\u1EC8': 'I',
+    '\u01CF': 'I',
+    '\u0208': 'I',
+    '\u020A': 'I',
+    '\u1ECA': 'I',
+    '\u012E': 'I',
+    '\u1E2C': 'I',
+    '\u0197': 'I',
+    '\u24BF': 'J',
+    '\uFF2A': 'J',
+    '\u0134': 'J',
+    '\u0248': 'J',
+    '\u24C0': 'K',
+    '\uFF2B': 'K',
+    '\u1E30': 'K',
+    '\u01E8': 'K',
+    '\u1E32': 'K',
+    '\u0136': 'K',
+    '\u1E34': 'K',
+    '\u0198': 'K',
+    '\u2C69': 'K',
+    '\uA740': 'K',
+    '\uA742': 'K',
+    '\uA744': 'K',
+    '\uA7A2': 'K',
+    '\u24C1': 'L',
+    '\uFF2C': 'L',
+    '\u013F': 'L',
+    '\u0139': 'L',
+    '\u013D': 'L',
+    '\u1E36': 'L',
+    '\u1E38': 'L',
+    '\u013B': 'L',
+    '\u1E3C': 'L',
+    '\u1E3A': 'L',
+    '\u0141': 'L',
+    '\u023D': 'L',
+    '\u2C62': 'L',
+    '\u2C60': 'L',
+    '\uA748': 'L',
+    '\uA746': 'L',
+    '\uA780': 'L',
+    '\u01C7': 'LJ',
+    '\u01C8': 'Lj',
+    '\u24C2': 'M',
+    '\uFF2D': 'M',
+    '\u1E3E': 'M',
+    '\u1E40': 'M',
+    '\u1E42': 'M',
+    '\u2C6E': 'M',
+    '\u019C': 'M',
+    '\u24C3': 'N',
+    '\uFF2E': 'N',
+    '\u01F8': 'N',
+    '\u0143': 'N',
+    '\u00D1': 'N',
+    '\u1E44': 'N',
+    '\u0147': 'N',
+    '\u1E46': 'N',
+    '\u0145': 'N',
+    '\u1E4A': 'N',
+    '\u1E48': 'N',
+    '\u0220': 'N',
+    '\u019D': 'N',
+    '\uA790': 'N',
+    '\uA7A4': 'N',
+    '\u01CA': 'NJ',
+    '\u01CB': 'Nj',
+    '\u24C4': 'O',
+    '\uFF2F': 'O',
+    '\u00D2': 'O',
+    '\u00D3': 'O',
+    '\u00D4': 'O',
+    '\u1ED2': 'O',
+    '\u1ED0': 'O',
+    '\u1ED6': 'O',
+    '\u1ED4': 'O',
+    '\u00D5': 'O',
+    '\u1E4C': 'O',
+    '\u022C': 'O',
+    '\u1E4E': 'O',
+    '\u014C': 'O',
+    '\u1E50': 'O',
+    '\u1E52': 'O',
+    '\u014E': 'O',
+    '\u022E': 'O',
+    '\u0230': 'O',
+    '\u00D6': 'O',
+    '\u022A': 'O',
+    '\u1ECE': 'O',
+    '\u0150': 'O',
+    '\u01D1': 'O',
+    '\u020C': 'O',
+    '\u020E': 'O',
+    '\u01A0': 'O',
+    '\u1EDC': 'O',
+    '\u1EDA': 'O',
+    '\u1EE0': 'O',
+    '\u1EDE': 'O',
+    '\u1EE2': 'O',
+    '\u1ECC': 'O',
+    '\u1ED8': 'O',
+    '\u01EA': 'O',
+    '\u01EC': 'O',
+    '\u00D8': 'O',
+    '\u01FE': 'O',
+    '\u0186': 'O',
+    '\u019F': 'O',
+    '\uA74A': 'O',
+    '\uA74C': 'O',
+    '\u01A2': 'OI',
+    '\uA74E': 'OO',
+    '\u0222': 'OU',
+    '\u24C5': 'P',
+    '\uFF30': 'P',
+    '\u1E54': 'P',
+    '\u1E56': 'P',
+    '\u01A4': 'P',
+    '\u2C63': 'P',
+    '\uA750': 'P',
+    '\uA752': 'P',
+    '\uA754': 'P',
+    '\u24C6': 'Q',
+    '\uFF31': 'Q',
+    '\uA756': 'Q',
+    '\uA758': 'Q',
+    '\u024A': 'Q',
+    '\u24C7': 'R',
+    '\uFF32': 'R',
+    '\u0154': 'R',
+    '\u1E58': 'R',
+    '\u0158': 'R',
+    '\u0210': 'R',
+    '\u0212': 'R',
+    '\u1E5A': 'R',
+    '\u1E5C': 'R',
+    '\u0156': 'R',
+    '\u1E5E': 'R',
+    '\u024C': 'R',
+    '\u2C64': 'R',
+    '\uA75A': 'R',
+    '\uA7A6': 'R',
+    '\uA782': 'R',
+    '\u24C8': 'S',
+    '\uFF33': 'S',
+    '\u1E9E': 'S',
+    '\u015A': 'S',
+    '\u1E64': 'S',
+    '\u015C': 'S',
+    '\u1E60': 'S',
+    '\u0160': 'S',
+    '\u1E66': 'S',
+    '\u1E62': 'S',
+    '\u1E68': 'S',
+    '\u0218': 'S',
+    '\u015E': 'S',
+    '\u2C7E': 'S',
+    '\uA7A8': 'S',
+    '\uA784': 'S',
+    '\u24C9': 'T',
+    '\uFF34': 'T',
+    '\u1E6A': 'T',
+    '\u0164': 'T',
+    '\u1E6C': 'T',
+    '\u021A': 'T',
+    '\u0162': 'T',
+    '\u1E70': 'T',
+    '\u1E6E': 'T',
+    '\u0166': 'T',
+    '\u01AC': 'T',
+    '\u01AE': 'T',
+    '\u023E': 'T',
+    '\uA786': 'T',
+    '\uA728': 'TZ',
+    '\u24CA': 'U',
+    '\uFF35': 'U',
+    '\u00D9': 'U',
+    '\u00DA': 'U',
+    '\u00DB': 'U',
+    '\u0168': 'U',
+    '\u1E78': 'U',
+    '\u016A': 'U',
+    '\u1E7A': 'U',
+    '\u016C': 'U',
+    '\u00DC': 'U',
+    '\u01DB': 'U',
+    '\u01D7': 'U',
+    '\u01D5': 'U',
+    '\u01D9': 'U',
+    '\u1EE6': 'U',
+    '\u016E': 'U',
+    '\u0170': 'U',
+    '\u01D3': 'U',
+    '\u0214': 'U',
+    '\u0216': 'U',
+    '\u01AF': 'U',
+    '\u1EEA': 'U',
+    '\u1EE8': 'U',
+    '\u1EEE': 'U',
+    '\u1EEC': 'U',
+    '\u1EF0': 'U',
+    '\u1EE4': 'U',
+    '\u1E72': 'U',
+    '\u0172': 'U',
+    '\u1E76': 'U',
+    '\u1E74': 'U',
+    '\u0244': 'U',
+    '\u24CB': 'V',
+    '\uFF36': 'V',
+    '\u1E7C': 'V',
+    '\u1E7E': 'V',
+    '\u01B2': 'V',
+    '\uA75E': 'V',
+    '\u0245': 'V',
+    '\uA760': 'VY',
+    '\u24CC': 'W',
+    '\uFF37': 'W',
+    '\u1E80': 'W',
+    '\u1E82': 'W',
+    '\u0174': 'W',
+    '\u1E86': 'W',
+    '\u1E84': 'W',
+    '\u1E88': 'W',
+    '\u2C72': 'W',
+    '\u24CD': 'X',
+    '\uFF38': 'X',
+    '\u1E8A': 'X',
+    '\u1E8C': 'X',
+    '\u24CE': 'Y',
+    '\uFF39': 'Y',
+    '\u1EF2': 'Y',
+    '\u00DD': 'Y',
+    '\u0176': 'Y',
+    '\u1EF8': 'Y',
+    '\u0232': 'Y',
+    '\u1E8E': 'Y',
+    '\u0178': 'Y',
+    '\u1EF6': 'Y',
+    '\u1EF4': 'Y',
+    '\u01B3': 'Y',
+    '\u024E': 'Y',
+    '\u1EFE': 'Y',
+    '\u24CF': 'Z',
+    '\uFF3A': 'Z',
+    '\u0179': 'Z',
+    '\u1E90': 'Z',
+    '\u017B': 'Z',
+    '\u017D': 'Z',
+    '\u1E92': 'Z',
+    '\u1E94': 'Z',
+    '\u01B5': 'Z',
+    '\u0224': 'Z',
+    '\u2C7F': 'Z',
+    '\u2C6B': 'Z',
+    '\uA762': 'Z',
+    '\u24D0': 'a',
+    '\uFF41': 'a',
+    '\u1E9A': 'a',
+    '\u00E0': 'a',
+    '\u00E1': 'a',
+    '\u00E2': 'a',
+    '\u1EA7': 'a',
+    '\u1EA5': 'a',
+    '\u1EAB': 'a',
+    '\u1EA9': 'a',
+    '\u00E3': 'a',
+    '\u0101': 'a',
+    '\u0103': 'a',
+    '\u1EB1': 'a',
+    '\u1EAF': 'a',
+    '\u1EB5': 'a',
+    '\u1EB3': 'a',
+    '\u0227': 'a',
+    '\u01E1': 'a',
+    '\u00E4': 'a',
+    '\u01DF': 'a',
+    '\u1EA3': 'a',
+    '\u00E5': 'a',
+    '\u01FB': 'a',
+    '\u01CE': 'a',
+    '\u0201': 'a',
+    '\u0203': 'a',
+    '\u1EA1': 'a',
+    '\u1EAD': 'a',
+    '\u1EB7': 'a',
+    '\u1E01': 'a',
+    '\u0105': 'a',
+    '\u2C65': 'a',
+    '\u0250': 'a',
+    '\uA733': 'aa',
+    '\u00E6': 'ae',
+    '\u01FD': 'ae',
+    '\u01E3': 'ae',
+    '\uA735': 'ao',
+    '\uA737': 'au',
+    '\uA739': 'av',
+    '\uA73B': 'av',
+    '\uA73D': 'ay',
+    '\u24D1': 'b',
+    '\uFF42': 'b',
+    '\u1E03': 'b',
+    '\u1E05': 'b',
+    '\u1E07': 'b',
+    '\u0180': 'b',
+    '\u0183': 'b',
+    '\u0253': 'b',
+    '\u24D2': 'c',
+    '\uFF43': 'c',
+    '\u0107': 'c',
+    '\u0109': 'c',
+    '\u010B': 'c',
+    '\u010D': 'c',
+    '\u00E7': 'c',
+    '\u1E09': 'c',
+    '\u0188': 'c',
+    '\u023C': 'c',
+    '\uA73F': 'c',
+    '\u2184': 'c',
+    '\u24D3': 'd',
+    '\uFF44': 'd',
+    '\u1E0B': 'd',
+    '\u010F': 'd',
+    '\u1E0D': 'd',
+    '\u1E11': 'd',
+    '\u1E13': 'd',
+    '\u1E0F': 'd',
+    '\u0111': 'd',
+    '\u018C': 'd',
+    '\u0256': 'd',
+    '\u0257': 'd',
+    '\uA77A': 'd',
+    '\u01F3': 'dz',
+    '\u01C6': 'dz',
+    '\u24D4': 'e',
+    '\uFF45': 'e',
+    '\u00E8': 'e',
+    '\u00E9': 'e',
+    '\u00EA': 'e',
+    '\u1EC1': 'e',
+    '\u1EBF': 'e',
+    '\u1EC5': 'e',
+    '\u1EC3': 'e',
+    '\u1EBD': 'e',
+    '\u0113': 'e',
+    '\u1E15': 'e',
+    '\u1E17': 'e',
+    '\u0115': 'e',
+    '\u0117': 'e',
+    '\u00EB': 'e',
+    '\u1EBB': 'e',
+    '\u011B': 'e',
+    '\u0205': 'e',
+    '\u0207': 'e',
+    '\u1EB9': 'e',
+    '\u1EC7': 'e',
+    '\u0229': 'e',
+    '\u1E1D': 'e',
+    '\u0119': 'e',
+    '\u1E19': 'e',
+    '\u1E1B': 'e',
+    '\u0247': 'e',
+    '\u025B': 'e',
+    '\u01DD': 'e',
+    '\u24D5': 'f',
+    '\uFF46': 'f',
+    '\u1E1F': 'f',
+    '\u0192': 'f',
+    '\uA77C': 'f',
+    '\u24D6': 'g',
+    '\uFF47': 'g',
+    '\u01F5': 'g',
+    '\u011D': 'g',
+    '\u1E21': 'g',
+    '\u011F': 'g',
+    '\u0121': 'g',
+    '\u01E7': 'g',
+    '\u0123': 'g',
+    '\u01E5': 'g',
+    '\u0260': 'g',
+    '\uA7A1': 'g',
+    '\u1D79': 'g',
+    '\uA77F': 'g',
+    '\u24D7': 'h',
+    '\uFF48': 'h',
+    '\u0125': 'h',
+    '\u1E23': 'h',
+    '\u1E27': 'h',
+    '\u021F': 'h',
+    '\u1E25': 'h',
+    '\u1E29': 'h',
+    '\u1E2B': 'h',
+    '\u1E96': 'h',
+    '\u0127': 'h',
+    '\u2C68': 'h',
+    '\u2C76': 'h',
+    '\u0265': 'h',
+    '\u0195': 'hv',
+    '\u24D8': 'i',
+    '\uFF49': 'i',
+    '\u00EC': 'i',
+    '\u00ED': 'i',
+    '\u00EE': 'i',
+    '\u0129': 'i',
+    '\u012B': 'i',
+    '\u012D': 'i',
+    '\u00EF': 'i',
+    '\u1E2F': 'i',
+    '\u1EC9': 'i',
+    '\u01D0': 'i',
+    '\u0209': 'i',
+    '\u020B': 'i',
+    '\u1ECB': 'i',
+    '\u012F': 'i',
+    '\u1E2D': 'i',
+    '\u0268': 'i',
+    '\u0131': 'i',
+    '\u24D9': 'j',
+    '\uFF4A': 'j',
+    '\u0135': 'j',
+    '\u01F0': 'j',
+    '\u0249': 'j',
+    '\u24DA': 'k',
+    '\uFF4B': 'k',
+    '\u1E31': 'k',
+    '\u01E9': 'k',
+    '\u1E33': 'k',
+    '\u0137': 'k',
+    '\u1E35': 'k',
+    '\u0199': 'k',
+    '\u2C6A': 'k',
+    '\uA741': 'k',
+    '\uA743': 'k',
+    '\uA745': 'k',
+    '\uA7A3': 'k',
+    '\u24DB': 'l',
+    '\uFF4C': 'l',
+    '\u0140': 'l',
+    '\u013A': 'l',
+    '\u013E': 'l',
+    '\u1E37': 'l',
+    '\u1E39': 'l',
+    '\u013C': 'l',
+    '\u1E3D': 'l',
+    '\u1E3B': 'l',
+    '\u017F': 'l',
+    '\u0142': 'l',
+    '\u019A': 'l',
+    '\u026B': 'l',
+    '\u2C61': 'l',
+    '\uA749': 'l',
+    '\uA781': 'l',
+    '\uA747': 'l',
+    '\u01C9': 'lj',
+    '\u24DC': 'm',
+    '\uFF4D': 'm',
+    '\u1E3F': 'm',
+    '\u1E41': 'm',
+    '\u1E43': 'm',
+    '\u0271': 'm',
+    '\u026F': 'm',
+    '\u24DD': 'n',
+    '\uFF4E': 'n',
+    '\u01F9': 'n',
+    '\u0144': 'n',
+    '\u00F1': 'n',
+    '\u1E45': 'n',
+    '\u0148': 'n',
+    '\u1E47': 'n',
+    '\u0146': 'n',
+    '\u1E4B': 'n',
+    '\u1E49': 'n',
+    '\u019E': 'n',
+    '\u0272': 'n',
+    '\u0149': 'n',
+    '\uA791': 'n',
+    '\uA7A5': 'n',
+    '\u01CC': 'nj',
+    '\u24DE': 'o',
+    '\uFF4F': 'o',
+    '\u00F2': 'o',
+    '\u00F3': 'o',
+    '\u00F4': 'o',
+    '\u1ED3': 'o',
+    '\u1ED1': 'o',
+    '\u1ED7': 'o',
+    '\u1ED5': 'o',
+    '\u00F5': 'o',
+    '\u1E4D': 'o',
+    '\u022D': 'o',
+    '\u1E4F': 'o',
+    '\u014D': 'o',
+    '\u1E51': 'o',
+    '\u1E53': 'o',
+    '\u014F': 'o',
+    '\u022F': 'o',
+    '\u0231': 'o',
+    '\u00F6': 'o',
+    '\u022B': 'o',
+    '\u1ECF': 'o',
+    '\u0151': 'o',
+    '\u01D2': 'o',
+    '\u020D': 'o',
+    '\u020F': 'o',
+    '\u01A1': 'o',
+    '\u1EDD': 'o',
+    '\u1EDB': 'o',
+    '\u1EE1': 'o',
+    '\u1EDF': 'o',
+    '\u1EE3': 'o',
+    '\u1ECD': 'o',
+    '\u1ED9': 'o',
+    '\u01EB': 'o',
+    '\u01ED': 'o',
+    '\u00F8': 'o',
+    '\u01FF': 'o',
+    '\u0254': 'o',
+    '\uA74B': 'o',
+    '\uA74D': 'o',
+    '\u0275': 'o',
+    '\u01A3': 'oi',
+    '\u0223': 'ou',
+    '\uA74F': 'oo',
+    '\u24DF': 'p',
+    '\uFF50': 'p',
+    '\u1E55': 'p',
+    '\u1E57': 'p',
+    '\u01A5': 'p',
+    '\u1D7D': 'p',
+    '\uA751': 'p',
+    '\uA753': 'p',
+    '\uA755': 'p',
+    '\u24E0': 'q',
+    '\uFF51': 'q',
+    '\u024B': 'q',
+    '\uA757': 'q',
+    '\uA759': 'q',
+    '\u24E1': 'r',
+    '\uFF52': 'r',
+    '\u0155': 'r',
+    '\u1E59': 'r',
+    '\u0159': 'r',
+    '\u0211': 'r',
+    '\u0213': 'r',
+    '\u1E5B': 'r',
+    '\u1E5D': 'r',
+    '\u0157': 'r',
+    '\u1E5F': 'r',
+    '\u024D': 'r',
+    '\u027D': 'r',
+    '\uA75B': 'r',
+    '\uA7A7': 'r',
+    '\uA783': 'r',
+    '\u24E2': 's',
+    '\uFF53': 's',
+    '\u00DF': 's',
+    '\u015B': 's',
+    '\u1E65': 's',
+    '\u015D': 's',
+    '\u1E61': 's',
+    '\u0161': 's',
+    '\u1E67': 's',
+    '\u1E63': 's',
+    '\u1E69': 's',
+    '\u0219': 's',
+    '\u015F': 's',
+    '\u023F': 's',
+    '\uA7A9': 's',
+    '\uA785': 's',
+    '\u1E9B': 's',
+    '\u24E3': 't',
+    '\uFF54': 't',
+    '\u1E6B': 't',
+    '\u1E97': 't',
+    '\u0165': 't',
+    '\u1E6D': 't',
+    '\u021B': 't',
+    '\u0163': 't',
+    '\u1E71': 't',
+    '\u1E6F': 't',
+    '\u0167': 't',
+    '\u01AD': 't',
+    '\u0288': 't',
+    '\u2C66': 't',
+    '\uA787': 't',
+    '\uA729': 'tz',
+    '\u24E4': 'u',
+    '\uFF55': 'u',
+    '\u00F9': 'u',
+    '\u00FA': 'u',
+    '\u00FB': 'u',
+    '\u0169': 'u',
+    '\u1E79': 'u',
+    '\u016B': 'u',
+    '\u1E7B': 'u',
+    '\u016D': 'u',
+    '\u00FC': 'u',
+    '\u01DC': 'u',
+    '\u01D8': 'u',
+    '\u01D6': 'u',
+    '\u01DA': 'u',
+    '\u1EE7': 'u',
+    '\u016F': 'u',
+    '\u0171': 'u',
+    '\u01D4': 'u',
+    '\u0215': 'u',
+    '\u0217': 'u',
+    '\u01B0': 'u',
+    '\u1EEB': 'u',
+    '\u1EE9': 'u',
+    '\u1EEF': 'u',
+    '\u1EED': 'u',
+    '\u1EF1': 'u',
+    '\u1EE5': 'u',
+    '\u1E73': 'u',
+    '\u0173': 'u',
+    '\u1E77': 'u',
+    '\u1E75': 'u',
+    '\u0289': 'u',
+    '\u24E5': 'v',
+    '\uFF56': 'v',
+    '\u1E7D': 'v',
+    '\u1E7F': 'v',
+    '\u028B': 'v',
+    '\uA75F': 'v',
+    '\u028C': 'v',
+    '\uA761': 'vy',
+    '\u24E6': 'w',
+    '\uFF57': 'w',
+    '\u1E81': 'w',
+    '\u1E83': 'w',
+    '\u0175': 'w',
+    '\u1E87': 'w',
+    '\u1E85': 'w',
+    '\u1E98': 'w',
+    '\u1E89': 'w',
+    '\u2C73': 'w',
+    '\u24E7': 'x',
+    '\uFF58': 'x',
+    '\u1E8B': 'x',
+    '\u1E8D': 'x',
+    '\u24E8': 'y',
+    '\uFF59': 'y',
+    '\u1EF3': 'y',
+    '\u00FD': 'y',
+    '\u0177': 'y',
+    '\u1EF9': 'y',
+    '\u0233': 'y',
+    '\u1E8F': 'y',
+    '\u00FF': 'y',
+    '\u1EF7': 'y',
+    '\u1E99': 'y',
+    '\u1EF5': 'y',
+    '\u01B4': 'y',
+    '\u024F': 'y',
+    '\u1EFF': 'y',
+    '\u24E9': 'z',
+    '\uFF5A': 'z',
+    '\u017A': 'z',
+    '\u1E91': 'z',
+    '\u017C': 'z',
+    '\u017E': 'z',
+    '\u1E93': 'z',
+    '\u1E95': 'z',
+    '\u01B6': 'z',
+    '\u0225': 'z',
+    '\u0240': 'z',
+    '\u2C6C': 'z',
+    '\uA763': 'z',
+    '\u0386': '\u0391',
+    '\u0388': '\u0395',
+    '\u0389': '\u0397',
+    '\u038A': '\u0399',
+    '\u03AA': '\u0399',
+    '\u038C': '\u039F',
+    '\u038E': '\u03A5',
+    '\u03AB': '\u03A5',
+    '\u038F': '\u03A9',
+    '\u03AC': '\u03B1',
+    '\u03AD': '\u03B5',
+    '\u03AE': '\u03B7',
+    '\u03AF': '\u03B9',
+    '\u03CA': '\u03B9',
+    '\u0390': '\u03B9',
+    '\u03CC': '\u03BF',
+    '\u03CD': '\u03C5',
+    '\u03CB': '\u03C5',
+    '\u03B0': '\u03C5',
+    '\u03C9': '\u03C9',
+    '\u03C2': '\u03C3'
+  };
+
+  return diacritics;
+});
+
+S2.define('select2/data/base',[
+  '../utils'
+], function (Utils) {
+  function BaseAdapter ($element, options) {
+    BaseAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(BaseAdapter, Utils.Observable);
+
+  BaseAdapter.prototype.current = function (callback) {
+    throw new Error('The `current` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.query = function (params, callback) {
+    throw new Error('The `query` method must be defined in child classes.');
+  };
+
+  BaseAdapter.prototype.bind = function (container, $container) {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.destroy = function () {
+    // Can be implemented in subclasses
+  };
+
+  BaseAdapter.prototype.generateResultId = function (container, data) {
+    var id = container.id + '-result-';
+
+    id += Utils.generateChars(4);
+
+    if (data.id != null) {
+      id += '-' + data.id.toString();
+    } else {
+      id += '-' + Utils.generateChars(4);
+    }
+    return id;
+  };
+
+  return BaseAdapter;
+});
+
+S2.define('select2/data/select',[
+  './base',
+  '../utils',
+  'jquery'
+], function (BaseAdapter, Utils, $) {
+  function SelectAdapter ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    SelectAdapter.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(SelectAdapter, BaseAdapter);
+
+  SelectAdapter.prototype.current = function (callback) {
+    var data = [];
+    var self = this;
+
+    this.$element.find(':selected').each(function () {
+      var $option = $(this);
+
+      var option = self.item($option);
+
+      data.push(option);
+    });
+
+    callback(data);
+  };
+
+  SelectAdapter.prototype.select = function (data) {
+    var self = this;
+
+    data.selected = true;
+
+    // If data.element is a DOM node, use it instead
+    if ($(data.element).is('option')) {
+      data.element.selected = true;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    if (this.$element.prop('multiple')) {
+      this.current(function (currentData) {
+        var val = [];
+
+        data = [data];
+        data.push.apply(data, currentData);
+
+        for (var d = 0; d < data.length; d++) {
+          var id = data[d].id;
+
+          if ($.inArray(id, val) === -1) {
+            val.push(id);
+          }
+        }
+
+        self.$element.val(val);
+        self.$element.trigger('change');
+      });
+    } else {
+      var val = data.id;
+
+      this.$element.val(val);
+      this.$element.trigger('change');
+    }
+  };
+
+  SelectAdapter.prototype.unselect = function (data) {
+    var self = this;
+
+    if (!this.$element.prop('multiple')) {
+      return;
+    }
+
+    data.selected = false;
+
+    if ($(data.element).is('option')) {
+      data.element.selected = false;
+
+      this.$element.trigger('change');
+
+      return;
+    }
+
+    this.current(function (currentData) {
+      var val = [];
+
+      for (var d = 0; d < currentData.length; d++) {
+        var id = currentData[d].id;
+
+        if (id !== data.id && $.inArray(id, val) === -1) {
+          val.push(id);
+        }
+      }
+
+      self.$element.val(val);
+
+      self.$element.trigger('change');
+    });
+  };
+
+  SelectAdapter.prototype.bind = function (container, $container) {
+    var self = this;
+
+    this.container = container;
+
+    container.on('select', function (params) {
+      self.select(params.data);
+    });
+
+    container.on('unselect', function (params) {
+      self.unselect(params.data);
+    });
+  };
+
+  SelectAdapter.prototype.destroy = function () {
+    // Remove anything added to child elements
+    this.$element.find('*').each(function () {
+      // Remove any custom data set by Select2
+      $.removeData(this, 'data');
+    });
+  };
+
+  SelectAdapter.prototype.query = function (params, callback) {
+    var data = [];
+    var self = this;
+
+    var $options = this.$element.children();
+
+    $options.each(function () {
+      var $option = $(this);
+
+      if (!$option.is('option') && !$option.is('optgroup')) {
+        return;
+      }
+
+      var option = self.item($option);
+
+      var matches = self.matches(params, option);
+
+      if (matches !== null) {
+        data.push(matches);
+      }
+    });
+
+    callback({
+      results: data
+    });
+  };
+
+  SelectAdapter.prototype.addOptions = function ($options) {
+    Utils.appendMany(this.$element, $options);
+  };
+
+  SelectAdapter.prototype.option = function (data) {
+    var option;
+
+    if (data.children) {
+      option = document.createElement('optgroup');
+      option.label = data.text;
+    } else {
+      option = document.createElement('option');
+
+      if (option.textContent !== undefined) {
+        option.textContent = data.text;
+      } else {
+        option.innerText = data.text;
+      }
+    }
+
+    if (data.id !== undefined) {
+      option.value = data.id;
+    }
+
+    if (data.disabled) {
+      option.disabled = true;
+    }
+
+    if (data.selected) {
+      option.selected = true;
+    }
+
+    if (data.title) {
+      option.title = data.title;
+    }
+
+    var $option = $(option);
+
+    var normalizedData = this._normalizeItem(data);
+    normalizedData.element = option;
+
+    // Override the option's data with the combined data
+    $.data(option, 'data', normalizedData);
+
+    return $option;
+  };
+
+  SelectAdapter.prototype.item = function ($option) {
+    var data = {};
+
+    data = $.data($option[0], 'data');
+
+    if (data != null) {
+      return data;
+    }
+
+    if ($option.is('option')) {
+      data = {
+        id: $option.val(),
+        text: $option.text(),
+        disabled: $option.prop('disabled'),
+        selected: $option.prop('selected'),
+        title: $option.prop('title')
+      };
+    } else if ($option.is('optgroup')) {
+      data = {
+        text: $option.prop('label'),
+        children: [],
+        title: $option.prop('title')
+      };
+
+      var $children = $option.children('option');
+      var children = [];
+
+      for (var c = 0; c < $children.length; c++) {
+        var $child = $($children[c]);
+
+        var child = this.item($child);
+
+        children.push(child);
+      }
+
+      data.children = children;
+    }
+
+    data = this._normalizeItem(data);
+    data.element = $option[0];
+
+    $.data($option[0], 'data', data);
+
+    return data;
+  };
+
+  SelectAdapter.prototype._normalizeItem = function (item) {
+    if (!$.isPlainObject(item)) {
+      item = {
+        id: item,
+        text: item
+      };
+    }
+
+    item = $.extend({}, {
+      text: ''
+    }, item);
+
+    var defaults = {
+      selected: false,
+      disabled: false
+    };
+
+    if (item.id != null) {
+      item.id = item.id.toString();
+    }
+
+    if (item.text != null) {
+      item.text = item.text.toString();
+    }
+
+    if (item._resultId == null && item.id && this.container != null) {
+      item._resultId = this.generateResultId(this.container, item);
+    }
+
+    return $.extend({}, defaults, item);
+  };
+
+  SelectAdapter.prototype.matches = function (params, data) {
+    var matcher = this.options.get('matcher');
+
+    return matcher(params, data);
+  };
+
+  return SelectAdapter;
+});
+
+S2.define('select2/data/array',[
+  './select',
+  '../utils',
+  'jquery'
+], function (SelectAdapter, Utils, $) {
+  function ArrayAdapter ($element, options) {
+    var data = options.get('data') || [];
+
+    ArrayAdapter.__super__.constructor.call(this, $element, options);
+
+    this.addOptions(this.convertToOptions(data));
+  }
+
+  Utils.Extend(ArrayAdapter, SelectAdapter);
+
+  ArrayAdapter.prototype.select = function (data) {
+    var $option = this.$element.find('option').filter(function (i, elm) {
+      return elm.value == data.id.toString();
+    });
+
+    if ($option.length === 0) {
+      $option = this.option(data);
+
+      this.addOptions($option);
+    }
+
+    ArrayAdapter.__super__.select.call(this, data);
+  };
+
+  ArrayAdapter.prototype.convertToOptions = function (data) {
+    var self = this;
+
+    var $existing = this.$element.find('option');
+    var existingIds = $existing.map(function () {
+      return self.item($(this)).id;
+    }).get();
+
+    var $options = [];
+
+    // Filter out all items except for the one passed in the argument
+    function onlyItem (item) {
+      return function () {
+        return $(this).val() == item.id;
+      };
+    }
+
+    for (var d = 0; d < data.length; d++) {
+      var item = this._normalizeItem(data[d]);
+
+      // Skip items which were pre-loaded, only merge the data
+      if ($.inArray(item.id, existingIds) >= 0) {
+        var $existingOption = $existing.filter(onlyItem(item));
+
+        var existingData = this.item($existingOption);
+        var newData = $.extend(true, {}, item, existingData);
+
+        var $newOption = this.option(newData);
+
+        $existingOption.replaceWith($newOption);
+
+        continue;
+      }
+
+      var $option = this.option(item);
+
+      if (item.children) {
+        var $children = this.convertToOptions(item.children);
+
+        Utils.appendMany($option, $children);
+      }
+
+      $options.push($option);
+    }
+
+    return $options;
+  };
+
+  return ArrayAdapter;
+});
+
+S2.define('select2/data/ajax',[
+  './array',
+  '../utils',
+  'jquery'
+], function (ArrayAdapter, Utils, $) {
+  function AjaxAdapter ($element, options) {
+    this.ajaxOptions = this._applyDefaults(options.get('ajax'));
+
+    if (this.ajaxOptions.processResults != null) {
+      this.processResults = this.ajaxOptions.processResults;
+    }
+
+    AjaxAdapter.__super__.constructor.call(this, $element, options);
+  }
+
+  Utils.Extend(AjaxAdapter, ArrayAdapter);
+
+  AjaxAdapter.prototype._applyDefaults = function (options) {
+    var defaults = {
+      data: function (params) {
+        return $.extend({}, params, {
+          q: params.term
+        });
+      },
+      transport: function (params, success, failure) {
+        var $request = $.ajax(params);
+
+        $request.then(success);
+        $request.fail(failure);
+
+        return $request;
+      }
+    };
+
+    return $.extend({}, defaults, options, true);
+  };
+
+  AjaxAdapter.prototype.processResults = function (results) {
+    return results;
+  };
+
+  AjaxAdapter.prototype.query = function (params, callback) {
+    var matches = [];
+    var self = this;
+
+    if (this._request != null) {
+      // JSONP requests cannot always be aborted
+      if ($.isFunction(this._request.abort)) {
+        this._request.abort();
+      }
+
+      this._request = null;
+    }
+
+    var options = $.extend({
+      type: 'GET'
+    }, this.ajaxOptions);
+
+    if (typeof options.url === 'function') {
+      options.url = options.url.call(this.$element, params);
+    }
+
+    if (typeof options.data === 'function') {
+      options.data = options.data.call(this.$element, params);
+    }
+
+    function request () {
+      var $request = options.transport(options, function (data) {
+        var results = self.processResults(data, params);
+
+        if (self.options.get('debug') && window.console && console.error) {
+          // Check to make sure that the response included a `results` key.
+          if (!results || !results.results || !$.isArray(results.results)) {
+            console.error(
+              'Select2: The AJAX results did not return an array in the ' +
+              '`results` key of the response.'
+            );
+          }
+        }
+
+        callback(results);
+      }, function () {
+        // Attempt to detect if a request was aborted
+        // Only works if the transport exposes a status property
+        if ($request.status && $request.status === '0') {
+          return;
+        }
+
+        self.trigger('results:message', {
+          message: 'errorLoading'
+        });
+      });
+
+      self._request = $request;
+    }
+
+    if (this.ajaxOptions.delay && params.term != null) {
+      if (this._queryTimeout) {
+        window.clearTimeout(this._queryTimeout);
+      }
+
+      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
+    } else {
+      request();
+    }
+  };
+
+  return AjaxAdapter;
+});
+
+S2.define('select2/data/tags',[
+  'jquery'
+], function ($) {
+  function Tags (decorated, $element, options) {
+    var tags = options.get('tags');
+
+    var createTag = options.get('createTag');
+
+    if (createTag !== undefined) {
+      this.createTag = createTag;
+    }
+
+    var insertTag = options.get('insertTag');
+
+    if (insertTag !== undefined) {
+        this.insertTag = insertTag;
+    }
+
+    decorated.call(this, $element, options);
+
+    if ($.isArray(tags)) {
+      for (var t = 0; t < tags.length; t++) {
+        var tag = tags[t];
+        var item = this._normalizeItem(tag);
+
+        var $option = this.option(item);
+
+        this.$element.append($option);
+      }
+    }
+  }
+
+  Tags.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    this._removeOldTags();
+
+    if (params.term == null || params.page != null) {
+      decorated.call(this, params, callback);
+      return;
+    }
+
+    function wrapper (obj, child) {
+      var data = obj.results;
+
+      for (var i = 0; i < data.length; i++) {
+        var option = data[i];
+
+        var checkChildren = (
+          option.children != null &&
+          !wrapper({
+            results: option.children
+          }, true)
+        );
+
+        var optionText = (option.text || '').toUpperCase();
+        var paramsTerm = (params.term || '').toUpperCase();
+
+        var checkText = optionText === paramsTerm;
+
+        if (checkText || checkChildren) {
+          if (child) {
+            return false;
+          }
+
+          obj.data = data;
+          callback(obj);
+
+          return;
+        }
+      }
+
+      if (child) {
+        return true;
+      }
+
+      var tag = self.createTag(params);
+
+      if (tag != null) {
+        var $option = self.option(tag);
+        $option.attr('data-select2-tag', true);
+
+        self.addOptions([$option]);
+
+        self.insertTag(data, tag);
+      }
+
+      obj.results = data;
+
+      callback(obj);
+    }
+
+    decorated.call(this, params, wrapper);
+  };
+
+  Tags.prototype.createTag = function (decorated, params) {
+    var term = $.trim(params.term);
+
+    if (term === '') {
+      return null;
+    }
+
+    return {
+      id: term,
+      text: term
+    };
+  };
+
+  Tags.prototype.insertTag = function (_, data, tag) {
+    data.unshift(tag);
+  };
+
+  Tags.prototype._removeOldTags = function (_) {
+    var tag = this._lastTag;
+
+    var $options = this.$element.find('option[data-select2-tag]');
+
+    $options.each(function () {
+      if (this.selected) {
+        return;
+      }
+
+      $(this).remove();
+    });
+  };
+
+  return Tags;
+});
+
+S2.define('select2/data/tokenizer',[
+  'jquery'
+], function ($) {
+  function Tokenizer (decorated, $element, options) {
+    var tokenizer = options.get('tokenizer');
+
+    if (tokenizer !== undefined) {
+      this.tokenizer = tokenizer;
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  Tokenizer.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    this.$search =  container.dropdown.$search || container.selection.$search ||
+      $container.find('.select2-search__field');
+  };
+
+  Tokenizer.prototype.query = function (decorated, params, callback) {
+    var self = this;
+
+    function createAndSelect (data) {
+      // Normalize the data object so we can use it for checks
+      var item = self._normalizeItem(data);
+
+      // Check if the data object already exists as a tag
+      // Select it if it doesn't
+      var $existingOptions = self.$element.find('option').filter(function () {
+        return $(this).val() === item.id;
+      });
+
+      // If an existing option wasn't found for it, create the option
+      if (!$existingOptions.length) {
+        var $option = self.option(item);
+        $option.attr('data-select2-tag', true);
+
+        self._removeOldTags();
+        self.addOptions([$option]);
+      }
+
+      // Select the item, now that we know there is an option for it
+      select(item);
+    }
+
+    function select (data) {
+      self.trigger('select', {
+        data: data
+      });
+    }
+
+    params.term = params.term || '';
+
+    var tokenData = this.tokenizer(params, this.options, createAndSelect);
+
+    if (tokenData.term !== params.term) {
+      // Replace the search term if we have the search box
+      if (this.$search.length) {
+        this.$search.val(tokenData.term);
+        this.$search.focus();
+      }
+
+      params.term = tokenData.term;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
+    var separators = options.get('tokenSeparators') || [];
+    var term = params.term;
+    var i = 0;
+
+    var createTag = this.createTag || function (params) {
+      return {
+        id: params.term,
+        text: params.term
+      };
+    };
+
+    while (i < term.length) {
+      var termChar = term[i];
+
+      if ($.inArray(termChar, separators) === -1) {
+        i++;
+
+        continue;
+      }
+
+      var part = term.substr(0, i);
+      var partParams = $.extend({}, params, {
+        term: part
+      });
+
+      var data = createTag(partParams);
+
+      if (data == null) {
+        i++;
+        continue;
+      }
+
+      callback(data);
+
+      // Reset the term to not include the tokenized portion
+      term = term.substr(i + 1) || '';
+      i = 0;
+    }
+
+    return {
+      term: term
+    };
+  };
+
+  return Tokenizer;
+});
+
+S2.define('select2/data/minimumInputLength',[
+
+], function () {
+  function MinimumInputLength (decorated, $e, options) {
+    this.minimumInputLength = options.get('minimumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MinimumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (params.term.length < this.minimumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooShort',
+        args: {
+          minimum: this.minimumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MinimumInputLength;
+});
+
+S2.define('select2/data/maximumInputLength',[
+
+], function () {
+  function MaximumInputLength (decorated, $e, options) {
+    this.maximumInputLength = options.get('maximumInputLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumInputLength.prototype.query = function (decorated, params, callback) {
+    params.term = params.term || '';
+
+    if (this.maximumInputLength > 0 &&
+        params.term.length > this.maximumInputLength) {
+      this.trigger('results:message', {
+        message: 'inputTooLong',
+        args: {
+          maximum: this.maximumInputLength,
+          input: params.term,
+          params: params
+        }
+      });
+
+      return;
+    }
+
+    decorated.call(this, params, callback);
+  };
+
+  return MaximumInputLength;
+});
+
+S2.define('select2/data/maximumSelectionLength',[
+
+], function (){
+  function MaximumSelectionLength (decorated, $e, options) {
+    this.maximumSelectionLength = options.get('maximumSelectionLength');
+
+    decorated.call(this, $e, options);
+  }
+
+  MaximumSelectionLength.prototype.query =
+    function (decorated, params, callback) {
+      var self = this;
+
+      this.current(function (currentData) {
+        var count = currentData != null ? currentData.length : 0;
+        if (self.maximumSelectionLength > 0 &&
+          count >= self.maximumSelectionLength) {
+          self.trigger('results:message', {
+            message: 'maximumSelected',
+            args: {
+              maximum: self.maximumSelectionLength
+            }
+          });
+          return;
+        }
+        decorated.call(self, params, callback);
+      });
+  };
+
+  return MaximumSelectionLength;
+});
+
+S2.define('select2/dropdown',[
+  'jquery',
+  './utils'
+], function ($, Utils) {
+  function Dropdown ($element, options) {
+    this.$element = $element;
+    this.options = options;
+
+    Dropdown.__super__.constructor.call(this);
+  }
+
+  Utils.Extend(Dropdown, Utils.Observable);
+
+  Dropdown.prototype.render = function () {
+    var $dropdown = $(
+      '<span class="select2-dropdown">' +
+        '<span class="select2-results"></span>' +
+      '</span>'
+    );
+
+    $dropdown.attr('dir', this.options.get('dir'));
+
+    this.$dropdown = $dropdown;
+
+    return $dropdown;
+  };
+
+  Dropdown.prototype.bind = function () {
+    // Should be implemented in subclasses
+  };
+
+  Dropdown.prototype.position = function ($dropdown, $container) {
+    // Should be implmented in subclasses
+  };
+
+  Dropdown.prototype.destroy = function () {
+    // Remove the dropdown from the DOM
+    this.$dropdown.remove();
+  };
+
+  return Dropdown;
+});
+
+S2.define('select2/dropdown/search',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function Search () { }
+
+  Search.prototype.render = function (decorated) {
+    var $rendered = decorated.call(this);
+
+    var $search = $(
+      '<span class="select2-search select2-search--dropdown">' +
+        '<input class="select2-search__field" type="search" tabindex="-1"' +
+        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
+        ' spellcheck="false" role="textbox" />' +
+      '</span>'
+    );
+
+    this.$searchContainer = $search;
+    this.$search = $search.find('input');
+
+    $rendered.prepend($search);
+
+    return $rendered;
+  };
+
+  Search.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    this.$search.on('keydown', function (evt) {
+      self.trigger('keypress', evt);
+
+      self._keyUpPrevented = evt.isDefaultPrevented();
+    });
+
+    // Workaround for browsers which do not support the `input` event
+    // This will prevent double-triggering of events for browsers which support
+    // both the `keyup` and `input` events.
+    this.$search.on('input', function (evt) {
+      // Unbind the duplicated `keyup` event
+      $(this).off('keyup');
+    });
+
+    this.$search.on('keyup input', function (evt) {
+      self.handleSearch(evt);
+    });
+
+    container.on('open', function () {
+      self.$search.attr('tabindex', 0);
+
+      self.$search.focus();
+
+      window.setTimeout(function () {
+        self.$search.focus();
+      }, 0);
+    });
+
+    container.on('close', function () {
+      self.$search.attr('tabindex', -1);
+
+      self.$search.val('');
+    });
+
+    container.on('focus', function () {
+      if (!container.isOpen()) {
+        self.$search.focus();
+      }
+    });
+
+    container.on('results:all', function (params) {
+      if (params.query.term == null || params.query.term === '') {
+        var showSearch = self.showSearch(params);
+
+        if (showSearch) {
+          self.$searchContainer.removeClass('select2-search--hide');
+        } else {
+          self.$searchContainer.addClass('select2-search--hide');
+        }
+      }
+    });
+  };
+
+  Search.prototype.handleSearch = function (evt) {
+    if (!this._keyUpPrevented) {
+      var input = this.$search.val();
+
+      this.trigger('query', {
+        term: input
+      });
+    }
+
+    this._keyUpPrevented = false;
+  };
+
+  Search.prototype.showSearch = function (_, params) {
+    return true;
+  };
+
+  return Search;
+});
+
+S2.define('select2/dropdown/hidePlaceholder',[
+
+], function () {
+  function HidePlaceholder (decorated, $element, options, dataAdapter) {
+    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  HidePlaceholder.prototype.append = function (decorated, data) {
+    data.results = this.removePlaceholder(data.results);
+
+    decorated.call(this, data);
+  };
+
+  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
+    if (typeof placeholder === 'string') {
+      placeholder = {
+        id: '',
+        text: placeholder
+      };
+    }
+
+    return placeholder;
+  };
+
+  HidePlaceholder.prototype.removePlaceholder = function (_, data) {
+    var modifiedData = data.slice(0);
+
+    for (var d = data.length - 1; d >= 0; d--) {
+      var item = data[d];
+
+      if (this.placeholder.id === item.id) {
+        modifiedData.splice(d, 1);
+      }
+    }
+
+    return modifiedData;
+  };
+
+  return HidePlaceholder;
+});
+
+S2.define('select2/dropdown/infiniteScroll',[
+  'jquery'
+], function ($) {
+  function InfiniteScroll (decorated, $element, options, dataAdapter) {
+    this.lastParams = {};
+
+    decorated.call(this, $element, options, dataAdapter);
+
+    this.$loadingMore = this.createLoadingMore();
+    this.loading = false;
+  }
+
+  InfiniteScroll.prototype.append = function (decorated, data) {
+    this.$loadingMore.remove();
+    this.loading = false;
+
+    decorated.call(this, data);
+
+    if (this.showLoadingMore(data)) {
+      this.$results.append(this.$loadingMore);
+    }
+  };
+
+  InfiniteScroll.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('query', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    container.on('query:append', function (params) {
+      self.lastParams = params;
+      self.loading = true;
+    });
+
+    this.$results.on('scroll', function () {
+      var isLoadMoreVisible = $.contains(
+        document.documentElement,
+        self.$loadingMore[0]
+      );
+
+      if (self.loading || !isLoadMoreVisible) {
+        return;
+      }
+
+      var currentOffset = self.$results.offset().top +
+        self.$results.outerHeight(false);
+      var loadingMoreOffset = self.$loadingMore.offset().top +
+        self.$loadingMore.outerHeight(false);
+
+      if (currentOffset + 50 >= loadingMoreOffset) {
+        self.loadMore();
+      }
+    });
+  };
+
+  InfiniteScroll.prototype.loadMore = function () {
+    this.loading = true;
+
+    var params = $.extend({}, {page: 1}, this.lastParams);
+
+    params.page++;
+
+    this.trigger('query:append', params);
+  };
+
+  InfiniteScroll.prototype.showLoadingMore = function (_, data) {
+    return data.pagination && data.pagination.more;
+  };
+
+  InfiniteScroll.prototype.createLoadingMore = function () {
+    var $option = $(
+      '<li ' +
+      'class="select2-results__option select2-results__option--load-more"' +
+      'role="treeitem" aria-disabled="true"></li>'
+    );
+
+    var message = this.options.get('translations').get('loadingMore');
+
+    $option.html(message(this.lastParams));
+
+    return $option;
+  };
+
+  return InfiniteScroll;
+});
+
+S2.define('select2/dropdown/attachBody',[
+  'jquery',
+  '../utils'
+], function ($, Utils) {
+  function AttachBody (decorated, $element, options) {
+    this.$dropdownParent = options.get('dropdownParent') || $(document.body);
+
+    decorated.call(this, $element, options);
+  }
+
+  AttachBody.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    var setupResultsEvents = false;
+
+    decorated.call(this, container, $container);
+
+    container.on('open', function () {
+      self._showDropdown();
+      self._attachPositioningHandler(container);
+
+      if (!setupResultsEvents) {
+        setupResultsEvents = true;
+
+        container.on('results:all', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+
+        container.on('results:append', function () {
+          self._positionDropdown();
+          self._resizeDropdown();
+        });
+      }
+    });
+
+    container.on('close', function () {
+      self._hideDropdown();
+      self._detachPositioningHandler(container);
+    });
+
+    this.$dropdownContainer.on('mousedown', function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  AttachBody.prototype.destroy = function (decorated) {
+    decorated.call(this);
+
+    this.$dropdownContainer.remove();
+  };
+
+  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
+    // Clone all of the container classes
+    $dropdown.attr('class', $container.attr('class'));
+
+    $dropdown.removeClass('select2');
+    $dropdown.addClass('select2-container--open');
+
+    $dropdown.css({
+      position: 'absolute',
+      top: -999999
+    });
+
+    this.$container = $container;
+  };
+
+  AttachBody.prototype.render = function (decorated) {
+    var $container = $('<span></span>');
+
+    var $dropdown = decorated.call(this);
+    $container.append($dropdown);
+
+    this.$dropdownContainer = $container;
+
+    return $container;
+  };
+
+  AttachBody.prototype._hideDropdown = function (decorated) {
+    this.$dropdownContainer.detach();
+  };
+
+  AttachBody.prototype._attachPositioningHandler =
+      function (decorated, container) {
+    var self = this;
+
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.each(function () {
+      $(this).data('select2-scroll-position', {
+        x: $(this).scrollLeft(),
+        y: $(this).scrollTop()
+      });
+    });
+
+    $watchers.on(scrollEvent, function (ev) {
+      var position = $(this).data('select2-scroll-position');
+      $(this).scrollTop(position.y);
+    });
+
+    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
+      function (e) {
+      self._positionDropdown();
+      self._resizeDropdown();
+    });
+  };
+
+  AttachBody.prototype._detachPositioningHandler =
+      function (decorated, container) {
+    var scrollEvent = 'scroll.select2.' + container.id;
+    var resizeEvent = 'resize.select2.' + container.id;
+    var orientationEvent = 'orientationchange.select2.' + container.id;
+
+    var $watchers = this.$container.parents().filter(Utils.hasScroll);
+    $watchers.off(scrollEvent);
+
+    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
+  };
+
+  AttachBody.prototype._positionDropdown = function () {
+    var $window = $(window);
+
+    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
+    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
+
+    var newDirection = null;
+
+    var offset = this.$container.offset();
+
+    offset.bottom = offset.top + this.$container.outerHeight(false);
+
+    var container = {
+      height: this.$container.outerHeight(false)
+    };
+
+    container.top = offset.top;
+    container.bottom = offset.top + container.height;
+
+    var dropdown = {
+      height: this.$dropdown.outerHeight(false)
+    };
+
+    var viewport = {
+      top: $window.scrollTop(),
+      bottom: $window.scrollTop() + $window.height()
+    };
+
+    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
+    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
+
+    var css = {
+      left: offset.left,
+      top: container.bottom
+    };
+
+    // Determine what the parent element is to use for calciulating the offset
+    var $offsetParent = this.$dropdownParent;
+
+    // For statically positoned elements, we need to get the element
+    // that is determining the offset
+    if ($offsetParent.css('position') === 'static') {
+      $offsetParent = $offsetParent.offsetParent();
+    }
+
+    var parentOffset = $offsetParent.offset();
+
+    css.top -= parentOffset.top;
+    css.left -= parentOffset.left;
+
+    if (!isCurrentlyAbove && !isCurrentlyBelow) {
+      newDirection = 'below';
+    }
+
+    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
+      newDirection = 'above';
+    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
+      newDirection = 'below';
+    }
+
+    if (newDirection == 'above' ||
+      (isCurrentlyAbove && newDirection !== 'below')) {
+      css.top = container.top - parentOffset.top - dropdown.height;
+    }
+
+    if (newDirection != null) {
+      this.$dropdown
+        .removeClass('select2-dropdown--below select2-dropdown--above')
+        .addClass('select2-dropdown--' + newDirection);
+      this.$container
+        .removeClass('select2-container--below select2-container--above')
+        .addClass('select2-container--' + newDirection);
+    }
+
+    this.$dropdownContainer.css(css);
+  };
+
+  AttachBody.prototype._resizeDropdown = function () {
+    var css = {
+      width: this.$container.outerWidth(false) + 'px'
+    };
+
+    if (this.options.get('dropdownAutoWidth')) {
+      css.minWidth = css.width;
+      css.position = 'relative';
+      css.width = 'auto';
+    }
+
+    this.$dropdown.css(css);
+  };
+
+  AttachBody.prototype._showDropdown = function (decorated) {
+    this.$dropdownContainer.appendTo(this.$dropdownParent);
+
+    this._positionDropdown();
+    this._resizeDropdown();
+  };
+
+  return AttachBody;
+});
+
+S2.define('select2/dropdown/minimumResultsForSearch',[
+
+], function () {
+  function countResults (data) {
+    var count = 0;
+
+    for (var d = 0; d < data.length; d++) {
+      var item = data[d];
+
+      if (item.children) {
+        count += countResults(item.children);
+      } else {
+        count++;
+      }
+    }
+
+    return count;
+  }
+
+  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
+    this.minimumResultsForSearch = options.get('minimumResultsForSearch');
+
+    if (this.minimumResultsForSearch < 0) {
+      this.minimumResultsForSearch = Infinity;
+    }
+
+    decorated.call(this, $element, options, dataAdapter);
+  }
+
+  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
+    if (countResults(params.data.results) < this.minimumResultsForSearch) {
+      return false;
+    }
+
+    return decorated.call(this, params);
+  };
+
+  return MinimumResultsForSearch;
+});
+
+S2.define('select2/dropdown/selectOnClose',[
+
+], function () {
+  function SelectOnClose () { }
+
+  SelectOnClose.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('close', function (params) {
+      self._handleSelectOnClose(params);
+    });
+  };
+
+  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
+    if (params && params.originalSelect2Event != null) {
+      var event = params.originalSelect2Event;
+
+      // Don't select an item if the close event was triggered from a select or
+      // unselect event
+      if (event._type === 'select' || event._type === 'unselect') {
+        return;
+      }
+    }
+
+    var $highlightedResults = this.getHighlightedResults();
+
+    // Only select highlighted results
+    if ($highlightedResults.length < 1) {
+      return;
+    }
+
+    var data = $highlightedResults.data('data');
+
+    // Don't re-select already selected resulte
+    if (
+      (data.element != null && data.element.selected) ||
+      (data.element == null && data.selected)
+    ) {
+      return;
+    }
+
+    this.trigger('select', {
+        data: data
+    });
+  };
+
+  return SelectOnClose;
+});
+
+S2.define('select2/dropdown/closeOnSelect',[
+
+], function () {
+  function CloseOnSelect () { }
+
+  CloseOnSelect.prototype.bind = function (decorated, container, $container) {
+    var self = this;
+
+    decorated.call(this, container, $container);
+
+    container.on('select', function (evt) {
+      self._selectTriggered(evt);
+    });
+
+    container.on('unselect', function (evt) {
+      self._selectTriggered(evt);
+    });
+  };
+
+  CloseOnSelect.prototype._selectTriggered = function (_, evt) {
+    var originalEvent = evt.originalEvent;
+
+    // Don't close if the control key is being held
+    if (originalEvent && originalEvent.ctrlKey) {
+      return;
+    }
+
+    this.trigger('close', {
+      originalEvent: originalEvent,
+      originalSelect2Event: evt
+    });
+  };
+
+  return CloseOnSelect;
+});
+
+S2.define('select2/i18n/en',[],function () {
+  // English
+  return {
+    errorLoading: function () {
+      return 'The results could not be loaded.';
+    },
+    inputTooLong: function (args) {
+      var overChars = args.input.length - args.maximum;
+
+      var message = 'Please delete ' + overChars + ' character';
+
+      if (overChars != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    inputTooShort: function (args) {
+      var remainingChars = args.minimum - args.input.length;
+
+      var message = 'Please enter ' + remainingChars + ' or more characters';
+
+      return message;
+    },
+    loadingMore: function () {
+      return 'Loading more results…';
+    },
+    maximumSelected: function (args) {
+      var message = 'You can only select ' + args.maximum + ' item';
+
+      if (args.maximum != 1) {
+        message += 's';
+      }
+
+      return message;
+    },
+    noResults: function () {
+      return 'No results found';
+    },
+    searching: function () {
+      return 'Searching…';
+    }
+  };
+});
+
+S2.define('select2/defaults',[
+  'jquery',
+  'require',
+
+  './results',
+
+  './selection/single',
+  './selection/multiple',
+  './selection/placeholder',
+  './selection/allowClear',
+  './selection/search',
+  './selection/eventRelay',
+
+  './utils',
+  './translation',
+  './diacritics',
+
+  './data/select',
+  './data/array',
+  './data/ajax',
+  './data/tags',
+  './data/tokenizer',
+  './data/minimumInputLength',
+  './data/maximumInputLength',
+  './data/maximumSelectionLength',
+
+  './dropdown',
+  './dropdown/search',
+  './dropdown/hidePlaceholder',
+  './dropdown/infiniteScroll',
+  './dropdown/attachBody',
+  './dropdown/minimumResultsForSearch',
+  './dropdown/selectOnClose',
+  './dropdown/closeOnSelect',
+
+  './i18n/en'
+], function ($, require,
+
+             ResultsList,
+
+             SingleSelection, MultipleSelection, Placeholder, AllowClear,
+             SelectionSearch, EventRelay,
+
+             Utils, Translation, DIACRITICS,
+
+             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
+             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
+
+             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
+             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
+
+             EnglishTranslation) {
+  function Defaults () {
+    this.reset();
+  }
+
+  Defaults.prototype.apply = function (options) {
+    options = $.extend(true, {}, this.defaults, options);
+
+    if (options.dataAdapter == null) {
+      if (options.ajax != null) {
+        options.dataAdapter = AjaxData;
+      } else if (options.data != null) {
+        options.dataAdapter = ArrayData;
+      } else {
+        options.dataAdapter = SelectData;
+      }
+
+      if (options.minimumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MinimumInputLength
+        );
+      }
+
+      if (options.maximumInputLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumInputLength
+        );
+      }
+
+      if (options.maximumSelectionLength > 0) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          MaximumSelectionLength
+        );
+      }
+
+      if (options.tags) {
+        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
+      }
+
+      if (options.tokenSeparators != null || options.tokenizer != null) {
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Tokenizer
+        );
+      }
+
+      if (options.query != null) {
+        var Query = require(options.amdBase + 'compat/query');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          Query
+        );
+      }
+
+      if (options.initSelection != null) {
+        var InitSelection = require(options.amdBase + 'compat/initSelection');
+
+        options.dataAdapter = Utils.Decorate(
+          options.dataAdapter,
+          InitSelection
+        );
+      }
+    }
+
+    if (options.resultsAdapter == null) {
+      options.resultsAdapter = ResultsList;
+
+      if (options.ajax != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          InfiniteScroll
+        );
+      }
+
+      if (options.placeholder != null) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          HidePlaceholder
+        );
+      }
+
+      if (options.selectOnClose) {
+        options.resultsAdapter = Utils.Decorate(
+          options.resultsAdapter,
+          SelectOnClose
+        );
+      }
+    }
+
+    if (options.dropdownAdapter == null) {
+      if (options.multiple) {
+        options.dropdownAdapter = Dropdown;
+      } else {
+        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
+
+        options.dropdownAdapter = SearchableDropdown;
+      }
+
+      if (options.minimumResultsForSearch !== 0) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          MinimumResultsForSearch
+        );
+      }
+
+      if (options.closeOnSelect) {
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          CloseOnSelect
+        );
+      }
+
+      if (
+        options.dropdownCssClass != null ||
+        options.dropdownCss != null ||
+        options.adaptDropdownCssClass != null
+      ) {
+        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
+
+        options.dropdownAdapter = Utils.Decorate(
+          options.dropdownAdapter,
+          DropdownCSS
+        );
+      }
+
+      options.dropdownAdapter = Utils.Decorate(
+        options.dropdownAdapter,
+        AttachBody
+      );
+    }
+
+    if (options.selectionAdapter == null) {
+      if (options.multiple) {
+        options.selectionAdapter = MultipleSelection;
+      } else {
+        options.selectionAdapter = SingleSelection;
+      }
+
+      // Add the placeholder mixin if a placeholder was specified
+      if (options.placeholder != null) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          Placeholder
+        );
+      }
+
+      if (options.allowClear) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          AllowClear
+        );
+      }
+
+      if (options.multiple) {
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          SelectionSearch
+        );
+      }
+
+      if (
+        options.containerCssClass != null ||
+        options.containerCss != null ||
+        options.adaptContainerCssClass != null
+      ) {
+        var ContainerCSS = require(options.amdBase + 'compat/containerCss');
+
+        options.selectionAdapter = Utils.Decorate(
+          options.selectionAdapter,
+          ContainerCSS
+        );
+      }
+
+      options.selectionAdapter = Utils.Decorate(
+        options.selectionAdapter,
+        EventRelay
+      );
+    }
+
+    if (typeof options.language === 'string') {
+      // Check if the language is specified with a region
+      if (options.language.indexOf('-') > 0) {
+        // Extract the region information if it is included
+        var languageParts = options.language.split('-');
+        var baseLanguage = languageParts[0];
+
+        options.language = [options.language, baseLanguage];
+      } else {
+        options.language = [options.language];
+      }
+    }
+
+    if ($.isArray(options.language)) {
+      var languages = new Translation();
+      options.language.push('en');
+
+      var languageNames = options.language;
+
+      for (var l = 0; l < languageNames.length; l++) {
+        var name = languageNames[l];
+        var language = {};
+
+        try {
+          // Try to load it with the original name
+          language = Translation.loadPath(name);
+        } catch (e) {
+          try {
+            // If we couldn't load it, check if it wasn't the full path
+            name = this.defaults.amdLanguageBase + name;
+            language = Translation.loadPath(name);
+          } catch (ex) {
+            // The translation could not be loaded at all. Sometimes this is
+            // because of a configuration problem, other times this can be
+            // because of how Select2 helps load all possible translation files.
+            if (options.debug && window.console && console.warn) {
+              console.warn(
+                'Select2: The language file for "' + name + '" could not be ' +
+                'automatically loaded. A fallback will be used instead.'
+              );
+            }
+
+            continue;
+          }
+        }
+
+        languages.extend(language);
+      }
+
+      options.translations = languages;
+    } else {
+      var baseTranslation = Translation.loadPath(
+        this.defaults.amdLanguageBase + 'en'
+      );
+      var customTranslation = new Translation(options.language);
+
+      customTranslation.extend(baseTranslation);
+
+      options.translations = customTranslation;
+    }
+
+    return options;
+  };
+
+  Defaults.prototype.reset = function () {
+    function stripDiacritics (text) {
+      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
+      function match(a) {
+        return DIACRITICS[a] || a;
+      }
+
+      return text.replace(/[^\u0000-\u007E]/g, match);
+    }
+
+    function matcher (params, data) {
+      // Always return the object if there is nothing to compare
+      if ($.trim(params.term) === '') {
+        return data;
+      }
+
+      // Do a recursive check for options with children
+      if (data.children && data.children.length > 0) {
+        // Clone the data object if there are children
+        // This is required as we modify the object to remove any non-matches
+        var match = $.extend(true, {}, data);
+
+        // Check each child of the option
+        for (var c = data.children.length - 1; c >= 0; c--) {
+          var child = data.children[c];
+
+          var matches = matcher(params, child);
+
+          // If there wasn't a match, remove the object in the array
+          if (matches == null) {
+            match.children.splice(c, 1);
+          }
+        }
+
+        // If any children matched, return the new object
+        if (match.children.length > 0) {
+          return match;
+        }
+
+        // If there were no matching children, check just the plain object
+        return matcher(params, match);
+      }
+
+      var original = stripDiacritics(data.text).toUpperCase();
+      var term = stripDiacritics(params.term).toUpperCase();
+
+      // Check if the text contains the term
+      if (original.indexOf(term) > -1) {
+        return data;
+      }
+
+      // If it doesn't contain the term, don't return anything
+      return null;
+    }
+
+    this.defaults = {
+      amdBase: './',
+      amdLanguageBase: './i18n/',
+      closeOnSelect: true,
+      debug: false,
+      dropdownAutoWidth: false,
+      escapeMarkup: Utils.escapeMarkup,
+      language: EnglishTranslation,
+      matcher: matcher,
+      minimumInputLength: 0,
+      maximumInputLength: 0,
+      maximumSelectionLength: 0,
+      minimumResultsForSearch: 0,
+      selectOnClose: false,
+      sorter: function (data) {
+        return data;
+      },
+      templateResult: function (result) {
+        return result.text;
+      },
+      templateSelection: function (selection) {
+        return selection.text;
+      },
+      theme: 'default',
+      width: 'resolve'
+    };
+  };
+
+  Defaults.prototype.set = function (key, value) {
+    var camelKey = $.camelCase(key);
+
+    var data = {};
+    data[camelKey] = value;
+
+    var convertedData = Utils._convertData(data);
+
+    $.extend(this.defaults, convertedData);
+  };
+
+  var defaults = new Defaults();
+
+  return defaults;
+});
+
+S2.define('select2/options',[
+  'require',
+  'jquery',
+  './defaults',
+  './utils'
+], function (require, $, Defaults, Utils) {
+  function Options (options, $element) {
+    this.options = options;
+
+    if ($element != null) {
+      this.fromElement($element);
+    }
+
+    this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
+  }
+
+  Options.prototype.fromElement = function ($e) {
+    var excludedData = ['select2'];
+
+    if (this.options.multiple == null) {
+      this.options.multiple = $e.prop('multiple');
+    }
+
+    if (this.options.disabled == null) {
+      this.options.disabled = $e.prop('disabled');
+    }
+
+    if (this.options.language == null) {
+      if ($e.prop('lang')) {
+        this.options.language = $e.prop('lang').toLowerCase();
+      } else if ($e.closest('[lang]').prop('lang')) {
+        this.options.language = $e.closest('[lang]').prop('lang');
+      }
+    }
+
+    if (this.options.dir == null) {
+      if ($e.prop('dir')) {
+        this.options.dir = $e.prop('dir');
+      } else if ($e.closest('[dir]').prop('dir')) {
+        this.options.dir = $e.closest('[dir]').prop('dir');
+      } else {
+        this.options.dir = 'ltr';
+      }
+    }
+
+    $e.prop('disabled', this.options.disabled);
+    $e.prop('multiple', this.options.multiple);
+
+    if ($e.data('select2Tags')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-select2-tags` attribute has been changed to ' +
+          'use the `data-data` and `data-tags="true"` attributes and will be ' +
+          'removed in future versions of Select2.'
+        );
+      }
+
+      $e.data('data', $e.data('select2Tags'));
+      $e.data('tags', true);
+    }
+
+    if ($e.data('ajaxUrl')) {
+      if (this.options.debug && window.console && console.warn) {
+        console.warn(
+          'Select2: The `data-ajax-url` attribute has been changed to ' +
+          '`data-ajax--url` and support for the old attribute will be removed' +
+          ' in future versions of Select2.'
+        );
+      }
+
+      $e.attr('ajax--url', $e.data('ajaxUrl'));
+      $e.data('ajax--url', $e.data('ajaxUrl'));
+    }
+
+    var dataset = {};
+
+    // Prefer the element's `dataset` attribute if it exists
+    // jQuery 1.x does not correctly handle data attributes with multiple dashes
+    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
+      dataset = $.extend(true, {}, $e[0].dataset, $e.data());
+    } else {
+      dataset = $e.data();
+    }
+
+    var data = $.extend(true, {}, dataset);
+
+    data = Utils._convertData(data);
+
+    for (var key in data) {
+      if ($.inArray(key, excludedData) > -1) {
+        continue;
+      }
+
+      if ($.isPlainObject(this.options[key])) {
+        $.extend(this.options[key], data[key]);
+      } else {
+        this.options[key] = data[key];
+      }
+    }
+
+    return this;
+  };
+
+  Options.prototype.get = function (key) {
+    return this.options[key];
+  };
+
+  Options.prototype.set = function (key, val) {
+    this.options[key] = val;
+  };
+
+  return Options;
+});
+
+S2.define('select2/core',[
+  'jquery',
+  './options',
+  './utils',
+  './keys'
+], function ($, Options, Utils, KEYS) {
+  var Select2 = function ($element, options) {
+    if ($element.data('select2') != null) {
+      $element.data('select2').destroy();
+    }
+
+    this.$element = $element;
+
+    this.id = this._generateId($element);
+
+    options = options || {};
+
+    this.options = new Options(options, $element);
+
+    Select2.__super__.constructor.call(this);
+
+    // Set up the tabindex
+
+    var tabindex = $element.attr('tabindex') || 0;
+    $element.data('old-tabindex', tabindex);
+    $element.attr('tabindex', '-1');
+
+    // Set up containers and adapters
+
+    var DataAdapter = this.options.get('dataAdapter');
+    this.dataAdapter = new DataAdapter($element, this.options);
+
+    var $container = this.render();
+
+    this._placeContainer($container);
+
+    var SelectionAdapter = this.options.get('selectionAdapter');
+    this.selection = new SelectionAdapter($element, this.options);
+    this.$selection = this.selection.render();
+
+    this.selection.position(this.$selection, $container);
+
+    var DropdownAdapter = this.options.get('dropdownAdapter');
+    this.dropdown = new DropdownAdapter($element, this.options);
+    this.$dropdown = this.dropdown.render();
+
+    this.dropdown.position(this.$dropdown, $container);
+
+    var ResultsAdapter = this.options.get('resultsAdapter');
+    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
+    this.$results = this.results.render();
+
+    this.results.position(this.$results, this.$dropdown);
+
+    // Bind events
+
+    var self = this;
+
+    // Bind the container to all of the adapters
+    this._bindAdapters();
+
+    // Register any DOM event handlers
+    this._registerDomEvents();
+
+    // Register any internal event handlers
+    this._registerDataEvents();
+    this._registerSelectionEvents();
+    this._registerDropdownEvents();
+    this._registerResultsEvents();
+    this._registerEvents();
+
+    // Set the initial state
+    this.dataAdapter.current(function (initialData) {
+      self.trigger('selection:update', {
+        data: initialData
+      });
+    });
+
+    // Hide the original select
+    $element.addClass('select2-hidden-accessible');
+    $element.attr('aria-hidden', 'true');
+
+    // Synchronize any monitored attributes
+    this._syncAttributes();
+
+    $element.data('select2', this);
+  };
+
+  Utils.Extend(Select2, Utils.Observable);
+
+  Select2.prototype._generateId = function ($element) {
+    var id = '';
+
+    if ($element.attr('id') != null) {
+      id = $element.attr('id');
+    } else if ($element.attr('name') != null) {
+      id = $element.attr('name') + '-' + Utils.generateChars(2);
+    } else {
+      id = Utils.generateChars(4);
+    }
+
+    id = id.replace(/(:|\.|\[|\]|,)/g, '');
+    id = 'select2-' + id;
+
+    return id;
+  };
+
+  Select2.prototype._placeContainer = function ($container) {
+    $container.insertAfter(this.$element);
+
+    var width = this._resolveWidth(this.$element, this.options.get('width'));
+
+    if (width != null) {
+      $container.css('width', width);
+    }
+  };
+
+  Select2.prototype._resolveWidth = function ($element, method) {
+    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
+
+    if (method == 'resolve') {
+      var styleWidth = this._resolveWidth($element, 'style');
+
+      if (styleWidth != null) {
+        return styleWidth;
+      }
+
+      return this._resolveWidth($element, 'element');
+    }
+
+    if (method == 'element') {
+      var elementWidth = $element.outerWidth(false);
+
+      if (elementWidth <= 0) {
+        return 'auto';
+      }
+
+      return elementWidth + 'px';
+    }
+
+    if (method == 'style') {
+      var style = $element.attr('style');
+
+      if (typeof(style) !== 'string') {
+        return null;
+      }
+
+      var attrs = style.split(';');
+
+      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
+        var attr = attrs[i].replace(/\s/g, '');
+        var matches = attr.match(WIDTH);
+
+        if (matches !== null && matches.length >= 1) {
+          return matches[1];
+        }
+      }
+
+      return null;
+    }
+
+    return method;
+  };
+
+  Select2.prototype._bindAdapters = function () {
+    this.dataAdapter.bind(this, this.$container);
+    this.selection.bind(this, this.$container);
+
+    this.dropdown.bind(this, this.$container);
+    this.results.bind(this, this.$container);
+  };
+
+  Select2.prototype._registerDomEvents = function () {
+    var self = this;
+
+    this.$element.on('change.select2', function () {
+      self.dataAdapter.current(function (data) {
+        self.trigger('selection:update', {
+          data: data
+        });
+      });
+    });
+
+    this.$element.on('focus.select2', function (evt) {
+      self.trigger('focus', evt);
+    });
+
+    this._syncA = Utils.bind(this._syncAttributes, this);
+    this._syncS = Utils.bind(this._syncSubtree, this);
+
+    if (this.$element[0].attachEvent) {
+      this.$element[0].attachEvent('onpropertychange', this._syncA);
+    }
+
+    var observer = window.MutationObserver ||
+      window.WebKitMutationObserver ||
+      window.MozMutationObserver
+    ;
+
+    if (observer != null) {
+      this._observer = new observer(function (mutations) {
+        $.each(mutations, self._syncA);
+        $.each(mutations, self._syncS);
+      });
+      this._observer.observe(this.$element[0], {
+        attributes: true,
+        childList: true,
+        subtree: false
+      });
+    } else if (this.$element[0].addEventListener) {
+      this.$element[0].addEventListener(
+        'DOMAttrModified',
+        self._syncA,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeInserted',
+        self._syncS,
+        false
+      );
+      this.$element[0].addEventListener(
+        'DOMNodeRemoved',
+        self._syncS,
+        false
+      );
+    }
+  };
+
+  Select2.prototype._registerDataEvents = function () {
+    var self = this;
+
+    this.dataAdapter.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerSelectionEvents = function () {
+    var self = this;
+    var nonRelayEvents = ['toggle', 'focus'];
+
+    this.selection.on('toggle', function () {
+      self.toggleDropdown();
+    });
+
+    this.selection.on('focus', function (params) {
+      self.focus(params);
+    });
+
+    this.selection.on('*', function (name, params) {
+      if ($.inArray(name, nonRelayEvents) !== -1) {
+        return;
+      }
+
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerDropdownEvents = function () {
+    var self = this;
+
+    this.dropdown.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerResultsEvents = function () {
+    var self = this;
+
+    this.results.on('*', function (name, params) {
+      self.trigger(name, params);
+    });
+  };
+
+  Select2.prototype._registerEvents = function () {
+    var self = this;
+
+    this.on('open', function () {
+      self.$container.addClass('select2-container--open');
+    });
+
+    this.on('close', function () {
+      self.$container.removeClass('select2-container--open');
+    });
+
+    this.on('enable', function () {
+      self.$container.removeClass('select2-container--disabled');
+    });
+
+    this.on('disable', function () {
+      self.$container.addClass('select2-container--disabled');
+    });
+
+    this.on('blur', function () {
+      self.$container.removeClass('select2-container--focus');
+    });
+
+    this.on('query', function (params) {
+      if (!self.isOpen()) {
+        self.trigger('open', {});
+      }
+
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:all', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('query:append', function (params) {
+      this.dataAdapter.query(params, function (data) {
+        self.trigger('results:append', {
+          data: data,
+          query: params
+        });
+      });
+    });
+
+    this.on('keypress', function (evt) {
+      var key = evt.which;
+
+      if (self.isOpen()) {
+        if (key === KEYS.ESC || key === KEYS.TAB ||
+            (key === KEYS.UP && evt.altKey)) {
+          self.close();
+
+          evt.preventDefault();
+        } else if (key === KEYS.ENTER) {
+          self.trigger('results:select', {});
+
+          evt.preventDefault();
+        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
+          self.trigger('results:toggle', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.UP) {
+          self.trigger('results:previous', {});
+
+          evt.preventDefault();
+        } else if (key === KEYS.DOWN) {
+          self.trigger('results:next', {});
+
+          evt.preventDefault();
+        }
+      } else {
+        if (key === KEYS.ENTER || key === KEYS.SPACE ||
+            (key === KEYS.DOWN && evt.altKey)) {
+          self.open();
+
+          evt.preventDefault();
+        }
+      }
+    });
+  };
+
+  Select2.prototype._syncAttributes = function () {
+    this.options.set('disabled', this.$element.prop('disabled'));
+
+    if (this.options.get('disabled')) {
+      if (this.isOpen()) {
+        this.close();
+      }
+
+      this.trigger('disable', {});
+    } else {
+      this.trigger('enable', {});
+    }
+  };
+
+  Select2.prototype._syncSubtree = function (evt, mutations) {
+    var changed = false;
+    var self = this;
+
+    // Ignore any mutation events raised for elements that aren't options or
+    // optgroups. This handles the case when the select element is destroyed
+    if (
+      evt && evt.target && (
+        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
+      )
+    ) {
+      return;
+    }
+
+    if (!mutations) {
+      // If mutation events aren't supported, then we can only assume that the
+      // change affected the selections
+      changed = true;
+    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
+      for (var n = 0; n < mutations.addedNodes.length; n++) {
+        var node = mutations.addedNodes[n];
+
+        if (node.selected) {
+          changed = true;
+        }
+      }
+    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
+      changed = true;
+    }
+
+    // Only re-pull the data if we think there is a change
+    if (changed) {
+      this.dataAdapter.current(function (currentData) {
+        self.trigger('selection:update', {
+          data: currentData
+        });
+      });
+    }
+  };
+
+  /**
+   * Override the trigger method to automatically trigger pre-events when
+   * there are events that can be prevented.
+   */
+  Select2.prototype.trigger = function (name, args) {
+    var actualTrigger = Select2.__super__.trigger;
+    var preTriggerMap = {
+      'open': 'opening',
+      'close': 'closing',
+      'select': 'selecting',
+      'unselect': 'unselecting'
+    };
+
+    if (args === undefined) {
+      args = {};
+    }
+
+    if (name in preTriggerMap) {
+      var preTriggerName = preTriggerMap[name];
+      var preTriggerArgs = {
+        prevented: false,
+        name: name,
+        args: args
+      };
+
+      actualTrigger.call(this, preTriggerName, preTriggerArgs);
+
+      if (preTriggerArgs.prevented) {
+        args.prevented = true;
+
+        return;
+      }
+    }
+
+    actualTrigger.call(this, name, args);
+  };
+
+  Select2.prototype.toggleDropdown = function () {
+    if (this.options.get('disabled')) {
+      return;
+    }
+
+    if (this.isOpen()) {
+      this.close();
+    } else {
+      this.open();
+    }
+  };
+
+  Select2.prototype.open = function () {
+    if (this.isOpen()) {
+      return;
+    }
+
+    this.trigger('query', {});
+  };
+
+  Select2.prototype.close = function () {
+    if (!this.isOpen()) {
+      return;
+    }
+
+    this.trigger('close', {});
+  };
+
+  Select2.prototype.isOpen = function () {
+    return this.$container.hasClass('select2-container--open');
+  };
+
+  Select2.prototype.hasFocus = function () {
+    return this.$container.hasClass('select2-container--focus');
+  };
+
+  Select2.prototype.focus = function (data) {
+    // No need to re-trigger focus events if we are already focused
+    if (this.hasFocus()) {
+      return;
+    }
+
+    this.$container.addClass('select2-container--focus');
+    this.trigger('focus', {});
+  };
+
+  Select2.prototype.enable = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("enable")` method has been deprecated and will' +
+        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
+        ' instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      args = [true];
+    }
+
+    var disabled = !args[0];
+
+    this.$element.prop('disabled', disabled);
+  };
+
+  Select2.prototype.data = function () {
+    if (this.options.get('debug') &&
+        arguments.length > 0 && window.console && console.warn) {
+      console.warn(
+        'Select2: Data can no longer be set using `select2("data")`. You ' +
+        'should consider setting the value instead using `$element.val()`.'
+      );
+    }
+
+    var data = [];
+
+    this.dataAdapter.current(function (currentData) {
+      data = currentData;
+    });
+
+    return data;
+  };
+
+  Select2.prototype.val = function (args) {
+    if (this.options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `select2("val")` method has been deprecated and will be' +
+        ' removed in later Select2 versions. Use $element.val() instead.'
+      );
+    }
+
+    if (args == null || args.length === 0) {
+      return this.$element.val();
+    }
+
+    var newVal = args[0];
+
+    if ($.isArray(newVal)) {
+      newVal = $.map(newVal, function (obj) {
+        return obj.toString();
+      });
+    }
+
+    this.$element.val(newVal).trigger('change');
+  };
+
+  Select2.prototype.destroy = function () {
+    this.$container.remove();
+
+    if (this.$element[0].detachEvent) {
+      this.$element[0].detachEvent('onpropertychange', this._syncA);
+    }
+
+    if (this._observer != null) {
+      this._observer.disconnect();
+      this._observer = null;
+    } else if (this.$element[0].removeEventListener) {
+      this.$element[0]
+        .removeEventListener('DOMAttrModified', this._syncA, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeInserted', this._syncS, false);
+      this.$element[0]
+        .removeEventListener('DOMNodeRemoved', this._syncS, false);
+    }
+
+    this._syncA = null;
+    this._syncS = null;
+
+    this.$element.off('.select2');
+    this.$element.attr('tabindex', this.$element.data('old-tabindex'));
+
+    this.$element.removeClass('select2-hidden-accessible');
+    this.$element.attr('aria-hidden', 'false');
+    this.$element.removeData('select2');
+
+    this.dataAdapter.destroy();
+    this.selection.destroy();
+    this.dropdown.destroy();
+    this.results.destroy();
+
+    this.dataAdapter = null;
+    this.selection = null;
+    this.dropdown = null;
+    this.results = null;
+  };
+
+  Select2.prototype.render = function () {
+    var $container = $(
+      '<span class="select2 select2-container">' +
+        '<span class="selection"></span>' +
+        '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
+      '</span>'
+    );
+
+    $container.attr('dir', this.options.get('dir'));
+
+    this.$container = $container;
+
+    this.$container.addClass('select2-container--' + this.options.get('theme'));
+
+    $container.data('element', this.$element);
+
+    return $container;
+  };
+
+  return Select2;
+});
+
+S2.define('select2/compat/utils',[
+  'jquery'
+], function ($) {
+  function syncCssClasses ($dest, $src, adapter) {
+    var classes, replacements = [], adapted;
+
+    classes = $.trim($dest.attr('class'));
+
+    if (classes) {
+      classes = '' + classes; // for IE which returns object
+
+      $(classes.split(/\s+/)).each(function () {
+        // Save all Select2 classes
+        if (this.indexOf('select2-') === 0) {
+          replacements.push(this);
+        }
+      });
+    }
+
+    classes = $.trim($src.attr('class'));
+
+    if (classes) {
+      classes = '' + classes; // for IE which returns object
+
+      $(classes.split(/\s+/)).each(function () {
+        // Only adapt non-Select2 classes
+        if (this.indexOf('select2-') !== 0) {
+          adapted = adapter(this);
+
+          if (adapted != null) {
+            replacements.push(adapted);
+          }
+        }
+      });
+    }
+
+    $dest.attr('class', replacements.join(' '));
+  }
+
+  return {
+    syncCssClasses: syncCssClasses
+  };
+});
+
+S2.define('select2/compat/containerCss',[
+  'jquery',
+  './utils'
+], function ($, CompatUtils) {
+  // No-op CSS adapter that discards all classes by default
+  function _containerAdapter (clazz) {
+    return null;
+  }
+
+  function ContainerCSS () { }
+
+  ContainerCSS.prototype.render = function (decorated) {
+    var $container = decorated.call(this);
+
+    var containerCssClass = this.options.get('containerCssClass') || '';
+
+    if ($.isFunction(containerCssClass)) {
+      containerCssClass = containerCssClass(this.$element);
+    }
+
+    var containerCssAdapter = this.options.get('adaptContainerCssClass');
+    containerCssAdapter = containerCssAdapter || _containerAdapter;
+
+    if (containerCssClass.indexOf(':all:') !== -1) {
+      containerCssClass = containerCssClass.replace(':all:', '');
+
+      var _cssAdapter = containerCssAdapter;
+
+      containerCssAdapter = function (clazz) {
+        var adapted = _cssAdapter(clazz);
+
+        if (adapted != null) {
+          // Append the old one along with the adapted one
+          return adapted + ' ' + clazz;
+        }
+
+        return clazz;
+      };
+    }
+
+    var containerCss = this.options.get('containerCss') || {};
+
+    if ($.isFunction(containerCss)) {
+      containerCss = containerCss(this.$element);
+    }
+
+    CompatUtils.syncCssClasses($container, this.$element, containerCssAdapter);
+
+    $container.css(containerCss);
+    $container.addClass(containerCssClass);
+
+    return $container;
+  };
+
+  return ContainerCSS;
+});
+
+S2.define('select2/compat/dropdownCss',[
+  'jquery',
+  './utils'
+], function ($, CompatUtils) {
+  // No-op CSS adapter that discards all classes by default
+  function _dropdownAdapter (clazz) {
+    return null;
+  }
+
+  function DropdownCSS () { }
+
+  DropdownCSS.prototype.render = function (decorated) {
+    var $dropdown = decorated.call(this);
+
+    var dropdownCssClass = this.options.get('dropdownCssClass') || '';
+
+    if ($.isFunction(dropdownCssClass)) {
+      dropdownCssClass = dropdownCssClass(this.$element);
+    }
+
+    var dropdownCssAdapter = this.options.get('adaptDropdownCssClass');
+    dropdownCssAdapter = dropdownCssAdapter || _dropdownAdapter;
+
+    if (dropdownCssClass.indexOf(':all:') !== -1) {
+      dropdownCssClass = dropdownCssClass.replace(':all:', '');
+
+      var _cssAdapter = dropdownCssAdapter;
+
+      dropdownCssAdapter = function (clazz) {
+        var adapted = _cssAdapter(clazz);
+
+        if (adapted != null) {
+          // Append the old one along with the adapted one
+          return adapted + ' ' + clazz;
+        }
+
+        return clazz;
+      };
+    }
+
+    var dropdownCss = this.options.get('dropdownCss') || {};
+
+    if ($.isFunction(dropdownCss)) {
+      dropdownCss = dropdownCss(this.$element);
+    }
+
+    CompatUtils.syncCssClasses($dropdown, this.$element, dropdownCssAdapter);
+
+    $dropdown.css(dropdownCss);
+    $dropdown.addClass(dropdownCssClass);
+
+    return $dropdown;
+  };
+
+  return DropdownCSS;
+});
+
+S2.define('select2/compat/initSelection',[
+  'jquery'
+], function ($) {
+  function InitSelection (decorated, $element, options) {
+    if (options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `initSelection` option has been deprecated in favor' +
+        ' of a custom data adapter that overrides the `current` method. ' +
+        'This method is now called multiple times instead of a single ' +
+        'time when the instance is initialized. Support will be removed ' +
+        'for the `initSelection` option in future versions of Select2'
+      );
+    }
+
+    this.initSelection = options.get('initSelection');
+    this._isInitialized = false;
+
+    decorated.call(this, $element, options);
+  }
+
+  InitSelection.prototype.current = function (decorated, callback) {
+    var self = this;
+
+    if (this._isInitialized) {
+      decorated.call(this, callback);
+
+      return;
+    }
+
+    this.initSelection.call(null, this.$element, function (data) {
+      self._isInitialized = true;
+
+      if (!$.isArray(data)) {
+        data = [data];
+      }
+
+      callback(data);
+    });
+  };
+
+  return InitSelection;
+});
+
+S2.define('select2/compat/inputData',[
+  'jquery'
+], function ($) {
+  function InputData (decorated, $element, options) {
+    this._currentData = [];
+    this._valueSeparator = options.get('valueSeparator') || ',';
+
+    if ($element.prop('type') === 'hidden') {
+      if (options.get('debug') && console && console.warn) {
+        console.warn(
+          'Select2: Using a hidden input with Select2 is no longer ' +
+          'supported and may stop working in the future. It is recommended ' +
+          'to use a `<select>` element instead.'
+        );
+      }
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  InputData.prototype.current = function (_, callback) {
+    function getSelected (data, selectedIds) {
+      var selected = [];
+
+      if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
+        data.selected = true;
+        selected.push(data);
+      } else {
+        data.selected = false;
+      }
+
+      if (data.children) {
+        selected.push.apply(selected, getSelected(data.children, selectedIds));
+      }
+
+      return selected;
+    }
+
+    var selected = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      selected.push.apply(
+        selected,
+        getSelected(
+          data,
+          this.$element.val().split(
+            this._valueSeparator
+          )
+        )
+      );
+    }
+
+    callback(selected);
+  };
+
+  InputData.prototype.select = function (_, data) {
+    if (!this.options.get('multiple')) {
+      this.current(function (allData) {
+        $.map(allData, function (data) {
+          data.selected = false;
+        });
+      });
+
+      this.$element.val(data.id);
+      this.$element.trigger('change');
+    } else {
+      var value = this.$element.val();
+      value += this._valueSeparator + data.id;
+
+      this.$element.val(value);
+      this.$element.trigger('change');
+    }
+  };
+
+  InputData.prototype.unselect = function (_, data) {
+    var self = this;
+
+    data.selected = false;
+
+    this.current(function (allData) {
+      var values = [];
+
+      for (var d = 0; d < allData.length; d++) {
+        var item = allData[d];
+
+        if (data.id == item.id) {
+          continue;
+        }
+
+        values.push(item.id);
+      }
+
+      self.$element.val(values.join(self._valueSeparator));
+      self.$element.trigger('change');
+    });
+  };
+
+  InputData.prototype.query = function (_, params, callback) {
+    var results = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      var matches = this.matches(params, data);
+
+      if (matches !== null) {
+        results.push(matches);
+      }
+    }
+
+    callback({
+      results: results
+    });
+  };
+
+  InputData.prototype.addOptions = function (_, $options) {
+    var options = $.map($options, function ($option) {
+      return $.data($option[0], 'data');
+    });
+
+    this._currentData.push.apply(this._currentData, options);
+  };
+
+  return InputData;
+});
+
+S2.define('select2/compat/matcher',[
+  'jquery'
+], function ($) {
+  function oldMatcher (matcher) {
+    function wrappedMatcher (params, data) {
+      var match = $.extend(true, {}, data);
+
+      if (params.term == null || $.trim(params.term) === '') {
+        return match;
+      }
+
+      if (data.children) {
+        for (var c = data.children.length - 1; c >= 0; c--) {
+          var child = data.children[c];
+
+          // Check if the child object matches
+          // The old matcher returned a boolean true or false
+          var doesMatch = matcher(params.term, child.text, child);
+
+          // If the child didn't match, pop it off
+          if (!doesMatch) {
+            match.children.splice(c, 1);
+          }
+        }
+
+        if (match.children.length > 0) {
+          return match;
+        }
+      }
+
+      if (matcher(params.term, data.text, data)) {
+        return match;
+      }
+
+      return null;
+    }
+
+    return wrappedMatcher;
+  }
+
+  return oldMatcher;
+});
+
+S2.define('select2/compat/query',[
+
+], function () {
+  function Query (decorated, $element, options) {
+    if (options.get('debug') && window.console && console.warn) {
+      console.warn(
+        'Select2: The `query` option has been deprecated in favor of a ' +
+        'custom data adapter that overrides the `query` method. Support ' +
+        'will be removed for the `query` option in future versions of ' +
+        'Select2.'
+      );
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  Query.prototype.query = function (_, params, callback) {
+    params.callback = callback;
+
+    var query = this.options.get('query');
+
+    query.call(null, params);
+  };
+
+  return Query;
+});
+
+S2.define('select2/dropdown/attachContainer',[
+
+], function () {
+  function AttachContainer (decorated, $element, options) {
+    decorated.call(this, $element, options);
+  }
+
+  AttachContainer.prototype.position =
+    function (decorated, $dropdown, $container) {
+    var $dropdownContainer = $container.find('.dropdown-wrapper');
+    $dropdownContainer.append($dropdown);
+
+    $dropdown.addClass('select2-dropdown--below');
+    $container.addClass('select2-container--below');
+  };
+
+  return AttachContainer;
+});
+
+S2.define('select2/dropdown/stopPropagation',[
+
+], function () {
+  function StopPropagation () { }
+
+  StopPropagation.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    var stoppedEvents = [
+    'blur',
+    'change',
+    'click',
+    'dblclick',
+    'focus',
+    'focusin',
+    'focusout',
+    'input',
+    'keydown',
+    'keyup',
+    'keypress',
+    'mousedown',
+    'mouseenter',
+    'mouseleave',
+    'mousemove',
+    'mouseover',
+    'mouseup',
+    'search',
+    'touchend',
+    'touchstart'
+    ];
+
+    this.$dropdown.on(stoppedEvents.join(' '), function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  return StopPropagation;
+});
+
+S2.define('select2/selection/stopPropagation',[
+
+], function () {
+  function StopPropagation () { }
+
+  StopPropagation.prototype.bind = function (decorated, container, $container) {
+    decorated.call(this, container, $container);
+
+    var stoppedEvents = [
+      'blur',
+      'change',
+      'click',
+      'dblclick',
+      'focus',
+      'focusin',
+      'focusout',
+      'input',
+      'keydown',
+      'keyup',
+      'keypress',
+      'mousedown',
+      'mouseenter',
+      'mouseleave',
+      'mousemove',
+      'mouseover',
+      'mouseup',
+      'search',
+      'touchend',
+      'touchstart'
+    ];
+
+    this.$selection.on(stoppedEvents.join(' '), function (evt) {
+      evt.stopPropagation();
+    });
+  };
+
+  return StopPropagation;
+});
+
+/*!
+ * jQuery Mousewheel 3.1.13
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ */
+
+(function (factory) {
+    if ( typeof S2.define === 'function' && S2.define.amd ) {
+        // AMD. Register as an anonymous module.
+        S2.define('jquery-mousewheel',['jquery'], factory);
+    } else if (typeof exports === 'object') {
+        // Node/CommonJS style for Browserify
+        module.exports = factory;
+    } else {
+        // Browser globals
+        factory(jQuery);
+    }
+}(function ($) {
+
+    var toFix  = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
+        toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ?
+                    ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
+        slice  = Array.prototype.slice,
+        nullLowestDeltaTimeout, lowestDelta;
+
+    if ( $.event.fixHooks ) {
+        for ( var i = toFix.length; i; ) {
+            $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
+        }
+    }
+
+    var special = $.event.special.mousewheel = {
+        version: '3.1.12',
+
+        setup: function() {
+            if ( this.addEventListener ) {
+                for ( var i = toBind.length; i; ) {
+                    this.addEventListener( toBind[--i], handler, false );
+                }
+            } else {
+                this.onmousewheel = handler;
+            }
+            // Store the line height and page height for this particular element
+            $.data(this, 'mousewheel-line-height', special.getLineHeight(this));
+            $.data(this, 'mousewheel-page-height', special.getPageHeight(this));
+        },
+
+        teardown: function() {
+            if ( this.removeEventListener ) {
+                for ( var i = toBind.length; i; ) {
+                    this.removeEventListener( toBind[--i], handler, false );
+                }
+            } else {
+                this.onmousewheel = null;
+            }
+            // Clean up the data we added to the element
+            $.removeData(this, 'mousewheel-line-height');
+            $.removeData(this, 'mousewheel-page-height');
+        },
+
+        getLineHeight: function(elem) {
+            var $elem = $(elem),
+                $parent = $elem['offsetParent' in $.fn ? 'offsetParent' : 'parent']();
+            if (!$parent.length) {
+                $parent = $('body');
+            }
+            return parseInt($parent.css('fontSize'), 10) || parseInt($elem.css('fontSize'), 10) || 16;
+        },
+
+        getPageHeight: function(elem) {
+            return $(elem).height();
+        },
+
+        settings: {
+            adjustOldDeltas: true, // see shouldAdjustOldDeltas() below
+            normalizeOffset: true  // calls getBoundingClientRect for each event
+        }
+    };
+
+    $.fn.extend({
+        mousewheel: function(fn) {
+            return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel');
+        },
+
+        unmousewheel: function(fn) {
+            return this.unbind('mousewheel', fn);
+        }
+    });
+
+
+    function handler(event) {
+        var orgEvent   = event || window.event,
+            args       = slice.call(arguments, 1),
+            delta      = 0,
+            deltaX     = 0,
+            deltaY     = 0,
+            absDelta   = 0,
+            offsetX    = 0,
+            offsetY    = 0;
+        event = $.event.fix(orgEvent);
+        event.type = 'mousewheel';
+
+        // Old school scrollwheel delta
+        if ( 'detail'      in orgEvent ) { deltaY = orgEvent.detail * -1;      }
+        if ( 'wheelDelta'  in orgEvent ) { deltaY = orgEvent.wheelDelta;       }
+        if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY;      }
+        if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; }
+
+        // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
+        if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
+            deltaX = deltaY * -1;
+            deltaY = 0;
+        }
+
+        // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
+        delta = deltaY === 0 ? deltaX : deltaY;
+
+        // New school wheel delta (wheel event)
+        if ( 'deltaY' in orgEvent ) {
+            deltaY = orgEvent.deltaY * -1;
+            delta  = deltaY;
+        }
+        if ( 'deltaX' in orgEvent ) {
+            deltaX = orgEvent.deltaX;
+            if ( deltaY === 0 ) { delta  = deltaX * -1; }
+        }
+
+        // No change actually happened, no reason to go any further
+        if ( deltaY === 0 && deltaX === 0 ) { return; }
+
+        // Need to convert lines and pages to pixels if we aren't already in pixels
+        // There are three delta modes:
+        //   * deltaMode 0 is by pixels, nothing to do
+        //   * deltaMode 1 is by lines
+        //   * deltaMode 2 is by pages
+        if ( orgEvent.deltaMode === 1 ) {
+            var lineHeight = $.data(this, 'mousewheel-line-height');
+            delta  *= lineHeight;
+            deltaY *= lineHeight;
+            deltaX *= lineHeight;
+        } else if ( orgEvent.deltaMode === 2 ) {
+            var pageHeight = $.data(this, 'mousewheel-page-height');
+            delta  *= pageHeight;
+            deltaY *= pageHeight;
+            deltaX *= pageHeight;
+        }
+
+        // Store lowest absolute delta to normalize the delta values
+        absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
+
+        if ( !lowestDelta || absDelta < lowestDelta ) {
+            lowestDelta = absDelta;
+
+            // Adjust older deltas if necessary
+            if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
+                lowestDelta /= 40;
+            }
+        }
+
+        // Adjust older deltas if necessary
+        if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
+            // Divide all the things by 40!
+            delta  /= 40;
+            deltaX /= 40;
+            deltaY /= 40;
+        }
+
+        // Get a whole, normalized value for the deltas
+        delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
+        deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
+        deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
+
+        // Normalise offsetX and offsetY properties
+        if ( special.settings.normalizeOffset && this.getBoundingClientRect ) {
+            var boundingRect = this.getBoundingClientRect();
+            offsetX = event.clientX - boundingRect.left;
+            offsetY = event.clientY - boundingRect.top;
+        }
+
+        // Add information to the event object
+        event.deltaX = deltaX;
+        event.deltaY = deltaY;
+        event.deltaFactor = lowestDelta;
+        event.offsetX = offsetX;
+        event.offsetY = offsetY;
+        // Go ahead and set deltaMode to 0 since we converted to pixels
+        // Although this is a little odd since we overwrite the deltaX/Y
+        // properties with normalized deltas.
+        event.deltaMode = 0;
+
+        // Add event and delta to the front of the arguments
+        args.unshift(event, delta, deltaX, deltaY);
+
+        // Clearout lowestDelta after sometime to better
+        // handle multiple device types that give different
+        // a different lowestDelta
+        // Ex: trackpad = 3 and mouse wheel = 120
+        if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
+        nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
+
+        return ($.event.dispatch || $.event.handle).apply(this, args);
+    }
+
+    function nullLowestDelta() {
+        lowestDelta = null;
+    }
+
+    function shouldAdjustOldDeltas(orgEvent, absDelta) {
+        // If this is an older event and the delta is divisable by 120,
+        // then we are assuming that the browser is treating this as an
+        // older mouse wheel event and that we should divide the deltas
+        // by 40 to try and get a more usable deltaFactor.
+        // Side note, this actually impacts the reported scroll distance
+        // in older browsers and can cause scrolling to be slower than native.
+        // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
+        return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
+    }
+
+}));
+
+S2.define('jquery.select2',[
+  'jquery',
+  'jquery-mousewheel',
+
+  './select2/core',
+  './select2/defaults'
+], function ($, _, Select2, Defaults) {
+  if ($.fn.select2 == null) {
+    // All methods that should return the element
+    var thisMethods = ['open', 'close', 'destroy'];
+
+    $.fn.select2 = function (options) {
+      options = options || {};
+
+      if (typeof options === 'object') {
+        this.each(function () {
+          var instanceOptions = $.extend(true, {}, options);
+
+          var instance = new Select2($(this), instanceOptions);
+        });
+
+        return this;
+      } else if (typeof options === 'string') {
+        var ret;
+        var args = Array.prototype.slice.call(arguments, 1);
+
+        this.each(function () {
+          var instance = $(this).data('select2');
+
+          if (instance == null && window.console && console.error) {
+            console.error(
+              'The select2(\'' + options + '\') method was called on an ' +
+              'element that is not using Select2.'
+            );
+          }
+
+          ret = instance[options].apply(instance, args);
+        });
+
+        // Check if we should be returning `this`
+        if ($.inArray(options, thisMethods) > -1) {
+          return this;
+        }
+
+        return ret;
+      } else {
+        throw new Error('Invalid arguments for Select2: ' + options);
+      }
+    };
+  }
+
+  if ($.fn.select2.defaults == null) {
+    $.fn.select2.defaults = Defaults;
+  }
+
+  return Select2;
+});
+
+  // Return the AMD loader configuration so it can be used outside of this file
+  return {
+    define: S2.define,
+    require: S2.require
+  };
+}());
+
+  // Autoload the jQuery bindings
+  // We know that all of the modules exist above this, so we're safe
+  var select2 = S2.require('jquery.select2');
+
+  // Hold the AMD module references on the jQuery function that was just loaded
+  // This allows Select2 to use the internal loader outside of this file, such
+  // as in the language files.
+  jQuery.fn.select2.amd = S2;
+
+  // Return the Select2 instance for anyone who is importing it.
+  return select2;
+}));

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
src/web/res/bower_components/select2/dist/js/select2.full.min.js


Неке датотеке нису приказане због велике количине промена