diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..087926a26
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,30 @@
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. import '...'
+2. input '....'
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Source Code**
+If applicable, add source code to help explain your problem.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. Windows,Linux]
+ - Python Version [e.g. 2.7, 3.6]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..8f1a3b79a
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+import six
+dictdata={u'Z':u'(',u'z':u'ผ',u'X':u')',u'x':u'ป',u'C':u'ฉ',u'c':u'แ',u'V':u'ฮ',u'v':u'อ',u'B':u'ฺ',u'b':u'ิ',u'N':u'์',u'n':u'ื',u'M':u'?',u'm':u'ท',u'<':u'ฒ',u',u':u'ม',u'>':u'ฬ',u'.':u'ใ',u'?':u'ฦ',u'/':u'ฝ',
+'A':u'ฤ',u'a':u'ฟ',u'S':u'ฆ',u's':u'ห',u'D':u'ฏ',u'd':u'ก',u'F':u'โ',u'f':u'ด',u'G':u'ฌ',u'g':u'เ',u'H':u'็',u'h':u'้',u'J':u'๋',u'j':u'j',u'K':u'ษ',u'k':u'า',u'L':u'ศ',u'l':u'ส',u':u':u'ซ',u'"':u'.',"'":"ง",u':u':u'ซ',u';':u'ว',
+'Q':u'๐',u'q':u'ๆ',u'W':u'"',u'w':u'ไ',u'E':u'ฎ',u'e':u'ำ',u'R':u'ฑ',u'r':u'พ',u'T':u'ธ',u't':u'ะ',u'Y':u'ํ',u'y':u'ั',u'U':u'๊',u'u':u'ี',u'I':u'ณ',u'i':u'ร',u'O':u'ฯ',u'o':u'น',u'P':u'ญ',u'p':u'ย',u'{':u'ฐ',u'[':u'บ',u'}':u',u',u']':u'ล',u'|':u'ฅ',u']':u'ฃ',
+'~':u'%',u'`':u'_',u'@':u'๑',u'2':u'/',u'#':u'๒',u'3':u'-',u'$':u'๓',u'4':u'ภ',u'%':u'๔',u'5':u'ถ',u'^':u'ู',u'6':u'ุ',u'&':u'฿',u'7':u'ึ',u'*':u'๕',u'8':u'ค',u'(':u'๖',u'9':u'ต',u')':u'๗',u'0':u'จ',u'_':u'๘',u'-':u'ข',u'+':u'๙',u'=':u'ช'}
+# แก้ไขพิมพ์ภาษาไทยผิดภาษา
+[docs]def texttothai(data):
+ """
+ :param str data: Incorrect input language correction (Needs thai but input english)
+ :return: thai text
+ """
+ data = list(data)
+ data2 = ""
+ for a in data:
+ if a in dictdata:
+ a = dictdata[a]
+ else:
+ a = a
+ data2+=a
+ del data
+ return data2
+# แก้ไขพิมพ์ภาษาอังกฤษผิดภาษา
+[docs]def texttoeng(data):
+ """
+ :param str data: Incorrect input language correction (Needs english but input thai)
+ :return: english text
+ """
+ data = list(data)
+ data2 = ""
+ dictdataeng= {v: k for k, v in six.iteritems(dictdata)}
+ for a in data:
+ if a in dictdataeng:
+ a = dictdataeng[a]
+ else:
+ a = a
+ data2+=a
+ return data2
+if __name__ == "__main__":
+ a="l;ylfu8iy["
+ a=texttothai(a)
+ a=texttothai(a)
+ b="นามรสนอำันี"
+ b=texttoeng(b)
+ six.print_(a)
+ six.print_(b)
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, unicode_literals, print_function
+import re
+
+try:
+ import icu
+ thkey = icu.Collator.createInstance(icu.Locale('th_TH')).getSortKey
+except ImportError:
+ def thkey(word):
+ cv = re.sub('[็-์]', '', word,re.U) # remove tone
+ cv = re.sub('([เ-ไ])([ก-ฮ])', '\\2\\1', cv,re.U) # switch lead vowel
+ tone = re.sub('[^็-์]', ' ', word,re.U) # just tone
+ return cv+tone
+
+[docs]def collation(data):
+ """
+ :param list data: a list of thai text
+ :return: a list of thai text, sorted alphabetically
+ **Example**::
+ >>> from pythainlp.collation import *
+ >>> collation(['ไก่', 'เป็ด', 'หมู', 'วัว'])
+ ['ไก่', 'เป็ด', 'วัว', 'หมู']
+ """
+ return sorted(data, key=thkey)
+
+if __name__ == "__main__":
+ a=collation(['ไก่','ไข่','ก','ฮา'])==['ก', 'ไก่', 'ไข่', 'ฮา']
+ print(a)
+ print(collation(['หลาย','หญิง'])==['หญิง','หลาย'])
+ print(collation(['ไก่', 'เป็ด', 'หมู', 'วัว'])==['ไก่', 'เป็ด', 'วัว', 'หมู'])
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+import datetime, pytz
+now1 = datetime.datetime.now()
+tz = pytz.timezone('Asia/Bangkok')
+
+[docs]def now():
+ """
+ :return: the current date with Thai month and Thai year. The month is spelled out in text, and the year is converted from AD to Thai years. (ie: 30 ตุลาคม 2560 20:45:30)
+ """
+ now1 = datetime.datetime.now(tz)
+ month_name = 'x มกราคม กุมภาพันธ์ มีนาคม เมษายน พฤษภาคม มิถุนายน กรกฎาคม สิงหาคม กันยายน ตุลาคม พฤศจิกายน ธันวาคม'.split()[now1.month]
+ thai_year = now1.year + 543
+ time_str = now1.strftime('%H:%M:%S')
+ return "%d %s %d %s"%(now1.day, month_name, thai_year, time_str) # 30 ตุลาคม 2560 20:45:30
+
+def now_reign_year():
+ '''
+ ปีรัชกาลที่ 10
+ ณ ปัจจุบัน
+ '''
+ return now1.year - 2015
+def reign_year_to_ad(reign_year,reign):
+ '''
+ ปีรัชกาล แปลงเป็น ค.ศ.
+ reign_year_to_ad(reign_year,reign)
+ reign_year - ปีที่
+ reign - รัชกาล
+ '''
+ if int(reign)==10:
+ ad = int(reign_year)+2015
+ elif int(reign)==9:
+ ad = int(reign_year)+1945
+ elif int(reign)==8:
+ ad = int(reign_year)+1928
+ elif int(reign)==7:
+ ad = int(reign_year)+1924
+ return ad
+# BE คือ พ.ศ.
+# AD คือ ค.ศ.
+# AH ปีฮิจเราะห์ศักราชเป็นปีพุทธศักราช จะต้องบวกด้วย 1122
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+# NLP
+import re
+from pythainlp.tokenize import word_tokenize
+from pythainlp.tag import pos_tag
+from pythainlp.corpus import stopwords
+thaicut="newmm" # ตัวตัดคำ
+# CRF
+try:
+ import sklearn_crfsuite
+except ImportError:
+ from pythainlp.tools import install_package
+ install_package('sklearn-crfsuite')
+ import sklearn_crfsuite
+# FILE
+import glob
+import codecs
+from pythainlp.corpus import get_file,download
+
+stopwords = stopwords.words('thai')
+
+
+def isThai(chr): # เช็คว่าเป็น char ภาษาไทย
+ cVal = ord(chr)
+ if(cVal >= 3584 and cVal <= 3711):
+ return True
+ return False
+def isThaiWord(word): # เช็คว่าเป็นคำภาษาไทย
+ t=True
+ for i in word:
+ l=isThai(i)
+ if l!=True and i!='.':
+ t=False
+ break
+ return t
+
+def is_stopword(word): # เช็คว่าเป็นคำฟุ่งเฟือง
+ return word in stopwords
+def doc2features(doc, i):
+ word = doc[i][0]
+ postag = doc[i][1]
+ # Features from current word
+ features={
+ 'word.word': word,
+ 'word.stopword': is_stopword(word),
+ 'word.isthai':isThaiWord(word),
+ 'word.isspace':word.isspace(),
+ 'postag':postag,
+ 'word.isdigit()': word.isdigit()
+ }
+ if word.isdigit() and len(word)==5:
+ features['word.islen5']=True
+ if i > 0:
+ prevword = doc[i-1][0]
+ postag1 = doc[i-1][1]
+ features['word.prevword'] = prevword
+ features['word.previsspace']=prevword.isspace()
+ features['word.previsthai']=isThaiWord(prevword)
+ features['word.prevstopword']=is_stopword(prevword)
+ features['word.prepostag'] = postag1
+ features['word.prevwordisdigit'] = prevword.isdigit()
+ else:
+ features['BOS'] = True # Special "Beginning of Sequence" tag
+ # Features from next word
+ if i < len(doc)-1:
+ nextword = doc[i+1][0]
+ postag1 = doc[i+1][1]
+ features['word.nextword'] = nextword
+ features['word.nextisspace']=nextword.isspace()
+ features['word.nextpostag'] = postag1
+ features['word.nextisthai']=isThaiWord(nextword)
+ features['word.nextstopword']=is_stopword(nextword)
+ features['word.nextwordisdigit'] = nextword.isdigit()
+ else:
+ features['EOS'] = True # Special "End of Sequence" tag
+ return features
+
+[docs]class thainer:
+ def __init__(self):
+ """
+ Thai NER
+ """
+ self.data_path = get_file('thainer')
+ if self.data_path==None:
+ download('thainer')
+ self.data_path = get_file('thainer')
+ self.crf=sklearn_crfsuite.CRF(
+ algorithm='lbfgs',
+ c1=0.1,
+ c2=0.1,
+ max_iterations=500,
+ all_possible_transitions=True,
+ model_filename=self.data_path)
+[docs] def get_ner(self,text,postag=True):
+ """
+ Get NER from Thai NER.
+
+ :param string text: thai text
+ :param boolean postag: get postag (True) or get not postag (False)
+
+ :return: list NER.
+
+ **Example**::
+ >>> from pythainlp.ner import thainer
+ >>> ner=thainer()
+ >>> ner.get_ner("วันที่ 15 ก.ย. 61 ทดสอบระบบเวลา 14:49 น.")
+ [('วันที่', 'JSBR', 'O'), (' ', 'NCMN', 'O'), ('15', 'NCNM', 'B-DATE'), (' ', 'NCMN', 'I-DATE'), ('ก.ย.', 'CMTR', 'I-DATE'), (' ', 'NCMN', 'I-DATE'), ('61', 'NCNM', 'I-DATE'), (' ', 'NCMN', 'O'), ('ทดสอบ', 'VACT', 'O'), ('ระบบ', 'NCMN', 'O'), ('เวลา', 'NCMN', 'O'), (' ', 'NCMN', 'O'), ('14', 'NCNM', 'B-TIME'), (':', 'PUNC', 'I-TIME'), ('49', 'NCNM', 'I-TIME'), (' ', 'NCMN', 'I-TIME'), ('น.', 'CMTR', 'I-TIME')]
+ >>> ner.get_ner("วันที่ 15 ก.ย. 61 ทดสอบระบบเวลา 14:49 น.",postag=False)
+ [('วันที่', 'O'), (' ', 'O'), ('15', 'B-DATE'), (' ', 'I-DATE'), ('ก.ย.', 'I-DATE'), (' ', 'I-DATE'), ('61', 'I-DATE'), (' ', 'O'), ('ทดสอบ', 'O'), ('ระบบ', 'O'), ('เวลา', 'O'), (' ', 'O'), ('14', 'B-TIME'), (':', 'I-TIME'), ('49', 'I-TIME'), (' ', 'I-TIME'), ('น.', 'I-TIME')]
+ """
+ self.word_cut=word_tokenize(text,engine=thaicut)
+ self.list_word=pos_tag(self.word_cut,engine='perceptron')
+ self.X_test = self.extract_features([(data,self.list_word[i][1]) for i,data in enumerate(self.word_cut)])
+ self.y_=self.crf.predict_single(self.X_test)
+ if postag:
+ return [(self.word_cut[i],self.list_word[i][1],data) for i,data in enumerate(self.y_)]
+ else:
+ return [(self.word_cut[i],data) for i,data in enumerate(self.y_)]
+ def extract_features(self,doc):
+ return [doc2features(doc, i) for i in range(len(doc))]
+ def get_labels(self,doc):
+ return [tag for (token,postag,tag) in doc]
+ def get_model(self):
+ return self.crf
+
+# -*- coding: utf-8 -*-
+''' ระบบแปลงเลขใน 1- 10 ภาษาไทย
+fork by http://justmindthought.blogspot.com/2012/12/code-php.html
+'''
+from __future__ import absolute_import,division,print_function,unicode_literals
+from builtins import dict
+from builtins import int
+import math,six,ast
+p = [[u'ภาษาไทย', u'ตัวเลข',u'เลขไทย'],
+ [u'หนึ่ง', u'1', u'๑'],
+ [u'สอง', u'2', u'๒'],
+ [u'สาม', u'3', u'๓'],
+ [u'สี่', u'4', u'๔'],
+ [u'ห้า', u'5', u'๕'],
+ [u'หก', u'6', u'๖'],
+ [u'หก', u'7', u'๗'],
+ [u'แปด', u'8', u'๘'],
+ [u'เก้า', u'9', u'๙']]
+thaitonum = dict((x[2], x[1]) for x in p[1:])
+p1 = dict((x[0], x[1]) for x in p[1:])
+d1 = 0
+#เลขไทยสู่เลข
+[docs]def thai_num_to_num(text):
+ """
+ :param str text: Thai number characters such as '๑', '๒', '๓'
+ :return: universal numbers such as '1', '2', '3'
+ """
+ thaitonum = dict((x[2], x[1]) for x in p[1:])
+ return thaitonum[text]
+
+[docs]def thai_num_to_text(text):
+ """
+ :param str text: Thai number characters such as '๑', '๒', '๓'
+ :return: Thai numbers, spelled out in Thai
+ """
+ thaitonum = dict((x[2], x[0]) for x in p[1:])
+ return thaitonum[text]
+
+[docs]def num_to_thai_num(text):
+ """
+ :param text: universal numbers such as '1', '2', '3'
+ :return: Thai number characters such as '๑', '๒', '๓'
+ """
+ thaitonum = dict((x[1], x[2]) for x in p[1:])
+ return thaitonum[text]
+
+[docs]def num_to_text(text):
+ """
+ :param text: universal numbers such as '1', '2', '3'
+ :return: Thai numbers, spelled out in Thai
+ """
+ thaitonum = dict((x[1], x[0]) for x in p[1:])
+ return thaitonum[text]
+
+[docs]def text_to_num(text):
+ """
+ :param text: Thai numbers, spelled out in Thai
+ :return: universal numbers such as '1', '2', '3'
+ """
+ thaitonum = dict((x[0], x[1]) for x in p[1:])
+ return thaitonum[text]
+
+def text_to_thai_num(text):
+ """
+ :param text: Thai numbers, spelled out in Thai
+ :return: Thai numbers such as '๑', '๒', '๓'
+ """
+ thaitonum = dict((x[0], x[2]) for x in p[1:])
+ return thaitonum[text]
+
+def number_format(num, places=0):
+ return '{:20,.2f}'.format(num)
+# fork by http://justmindthought.blogspot.com/2012/12/code-php.html
+
+[docs]def numtowords(amount_number):
+ amount_number = number_format(amount_number, 2).replace(" ","")
+ pt = amount_number.find(".")
+ number,fraction = "",""
+ amount_number1 = amount_number.split('.')
+ if (pt == False):
+ number = amount_number
+ else:
+ amount_number = amount_number.split('.')
+ number = amount_number[0]
+ fraction = int(amount_number1[1])
+ ret = ""
+ number=ast.literal_eval(number.replace(",",""))
+ baht = readnumber(number)
+ if (baht != ""):
+ ret += baht + "บาท"
+ satang = readnumber(fraction)
+ if (satang != ""):
+ ret += satang + "สตางค์"
+ else:
+ ret += "ถ้วน"
+ return ret
+
+def readnumber(number):
+ """
+ :param float number: a float number (with decimals) indicating a quantity
+ :return: a text that indicates the full amount in word form, properly ending each digit with the right term.
+ """
+ position_call = ["แสน", "หมื่น", "พัน", "ร้อย", "สิบ", ""]
+ number_call = ["", "หนึ่ง", "สอง", "สาม","สี่", "ห้า", "หก", "เจ็ด", "แปด", "เก้า"]
+ number = number
+ ret = ""
+ if (number == 0): return ret
+ if (number > 1000000):
+ ret += readnumber(int(number / 1000000)) + "ล้าน"
+ number = int(math.fmod(number, 1000000))
+ divider = 100000
+ pos = 0
+ while(number > 0):
+ d=int(number/divider)
+ if (divider == 10) and (d == 2):
+ ret += "ยี่"
+ elif (divider == 10) and (d == 1):
+ ret += ""
+ elif ((divider == 1) and (d == 1) and (ret != "")):
+ ret += "เอ็ด"
+ else:
+ ret += number_call[d]
+ if d:
+ ret += position_call[pos]
+ else:
+ ret += ""
+ number=number % divider
+ divider=divider / 10
+ pos += 1
+ return ret
+
+if __name__ == "__main__":
+ print(numtowords(4000.0))
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+from pythainlp.tokenize import word_tokenize
+# ถอดเสียงภาษาไทยเป็น Latin
+[docs]def romanization(data,engine='royin'):
+ """
+ :param str data: Thai text to be romanized
+ :param str engine: choose between 'royin' , 'pyicu' and 'thai2rom'. 'royin' will romanize according to the standard of Thai Royal Institute. 'pyicu' will romanize according to the Internaitonal Phonetic Alphabet. 'thai2rom' is deep learning thai romanization.
+ :return: English (more or less) text that spells out how the Thai text should read.
+ """
+ word_list=word_tokenize(data)
+ listword=[]
+ i=0
+ if engine=='royin':
+ from .royin import romanization
+ elif engine=='pyicu':
+ from .pyicu import romanization
+ elif engine=='thai2rom':
+ from pythainlp.romanization.thai2rom import thai2rom
+ thai=thai2rom()
+ return thai.romanization(data)
+ else:
+ raise Exception("error no have engine.")
+ while i<len(word_list):
+ listword.append(romanization(word_list[i]))
+ i+=1
+ return ''.join(listword)
+
+# -*- coding: utf-8 -*-
+from __future__ import print_function
+
+try:
+ import numpy as np
+ import keras
+except ImportError:
+ from pythainlp.tools import install_package
+ install_package('keras')
+ install_package('numpy')
+
+from pythainlp.corpus import get_file,download
+
+from keras.models import Model, load_model
+from keras.layers import Input
+import numpy as np
+[docs]class thai2rom:
+ def __init__(self):
+ '''
+ Thai2Rom
+ '''
+ self.batch_size = 64
+ self.epochs = 100
+ self.latent_dim = 256
+ self.num_samples = 648241
+ self.data_path = get_file('thai2rom-dataset')
+ if self.data_path==None:
+ download('thai2rom-dataset')
+ self.data_path = get_file('thai2rom-dataset')
+ self.input_texts = []
+ self.target_texts = []
+ self.input_characters = set()
+ self.target_characters = set()
+ with open(self.data_path, 'r', encoding='utf-8-sig') as self.f:
+ self.lines = self.f.read().split('\n')
+ for self.line in self.lines[: min(self.num_samples, len(self.lines) - 1)]:
+ self.input_text, self.target_text = self.line.split('\t')
+ if len(self.input_text)<30 and len(self.target_text)<90:
+ self.target_text = '\t' + self.target_text + '\n'
+ self.input_texts.append(self.input_text)
+ self.target_texts.append(self.target_text)
+ for self.char in self.input_text:
+ if self.char not in self.input_characters:
+ self.input_characters.add(self.char)
+ for self.char in self.target_text:
+ if self.char not in self.target_characters:
+ self.target_characters.add(self.char)
+ self.input_characters = sorted(list(self.input_characters))
+ self.target_characters = sorted(list(self.target_characters))
+ self.num_encoder_tokens = len(self.input_characters)
+ self.num_decoder_tokens = len(self.target_characters)
+ self.max_encoder_seq_length = max([len(self.txt) for self.txt in self.input_texts])
+ self.max_decoder_seq_length = max([len(self.txt) for self.txt in self.target_texts])
+ '''print('Number of samples:', len(self.input_texts))
+ print('Number of unique input tokens:', self.num_encoder_tokens)
+ print('Number of unique output tokens:', self.num_decoder_tokens)
+ print('Max sequence length for inputs:', self.max_encoder_seq_length)
+ print('Max sequence length for outputs:', self.max_decoder_seq_length)'''
+ self.input_token_index = dict([(char, i) for i, char in enumerate(self.input_characters)])
+ self.target_token_index = dict([(char, i) for i, char in enumerate(self.target_characters)])
+ self.encoder_input_data = np.zeros((len(self.input_texts), self.max_encoder_seq_length, self.num_encoder_tokens),dtype='float32')
+ for i, input_text in enumerate(self.input_texts):
+ for t, char in enumerate(self.input_text):
+ self.encoder_input_data[i, t, self.input_token_index[char]] = 1.
+ # Restore the model and construct the encoder and decoder.
+ self.filemodel=get_file('thai2rom')
+ if self.filemodel==None:
+ download('thai2rom')
+ self.filemodel=get_file('thai2rom')
+ self.model = load_model(self.filemodel)
+ self.encoder_inputs = self.model.input[0] # input_1
+ self.encoder_outputs, self.state_h_enc, self.state_c_enc = self.model.layers[2].output # lstm_1
+ self.encoder_states = [self.state_h_enc, self.state_c_enc]
+ self.encoder_model = Model(self.encoder_inputs, self.encoder_states)
+ self.decoder_inputs = self.model.input[1] # input_2
+ self.decoder_state_input_h = Input(shape=(self.latent_dim,), name='input_3')
+ self.decoder_state_input_c = Input(shape=(self.latent_dim,), name='input_4')
+ self.decoder_states_inputs = [self.decoder_state_input_h, self.decoder_state_input_c]
+ self.decoder_lstm = self.model.layers[3]
+ self.decoder_outputs, self.state_h_dec, self.state_c_dec = self.decoder_lstm(self.decoder_inputs, initial_state=self.decoder_states_inputs)
+ self.decoder_states = [self.state_h_dec, self.state_c_dec]
+ self.decoder_dense = self.model.layers[4]
+ self.decoder_outputs = self.decoder_dense(self.decoder_outputs)
+ self.decoder_model = Model([self.decoder_inputs] + self.decoder_states_inputs,[self.decoder_outputs] + self.decoder_states)
+
+ self.reverse_input_char_index = dict((i, char) for char, i in self.input_token_index.items())
+ self.reverse_target_char_index = dict((i, char) for char, i in self.target_token_index.items())
+ def decode_sequence(self,input_seq):
+ self.states_value = self.encoder_model.predict(input_seq)
+ self.target_seq = np.zeros((1, 1, self.num_decoder_tokens))
+ self.target_seq[0, 0, self.target_token_index['\t']] = 1.
+ self.stop_condition = False
+ self.decoded_sentence = ''
+ while not self.stop_condition:
+ self.output_tokens, self.h, self.c = self.decoder_model.predict([self.target_seq] + self.states_value)
+ self.sampled_token_index = np.argmax(self.output_tokens[0, -1, :])
+ self.sampled_char = self.reverse_target_char_index[self.sampled_token_index]
+ self.decoded_sentence += self.sampled_char
+ if (self.sampled_char == '\n' or len(self.decoded_sentence) > self.max_decoder_seq_length):
+ self.stop_condition = True
+ self.target_seq = np.zeros((1, 1, self.num_decoder_tokens))
+ self.target_seq[0, 0, self.sampled_token_index] = 1.
+ self.states_value = [self.h, self.c]
+ return self.decoded_sentence
+ def encode_input(self,name):
+ self.test_input = np.zeros((1, self.max_encoder_seq_length, self.num_encoder_tokens),dtype='float32')
+ for t, char in enumerate(name):
+ self.test_input[0, t, self.input_token_index[char]] = 1.
+ return self.test_input
+[docs] def romanization(self,text):
+ '''
+ :param str text: Thai text to be romanized
+ :return: English (more or less) text that spells out how the Thai text should read.
+ '''
+ return self.decode_sequence(self.encode_input(text))
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals,print_function
+import pythainlp
+from pythainlp.corpus import stopwords
+import os
+from pythainlp.tokenize import word_tokenize
+import dill
+
+templates_dir = os.path.join(os.path.dirname(pythainlp.__file__), 'sentiment')
+[docs]def sentiment(text, engine='old'):
+ """
+ :param str text: thai text
+ :param str engine: sentiment analysis engine (old or ulmfit)
+ :return: pos or neg
+
+ **Example**::
+ >>> from pythainlp.sentiment import sentiment
+ >>> text="วันนี้อากาศดีจัง"
+ >>> sentiment(text)
+ 'pos'
+ >>> sentiment(text,'ulmfit')
+ 'pos'
+ >>> text="วันนี้อารมณ์เสียมาก"
+ >>> sentiment(text)
+ 'neg'
+ >>> sentiment(text,'ulmfit')
+ 'neg'
+ """
+ if engine=='old':
+ with open(os.path.join(templates_dir, 'vocabulary.data'), 'rb') as in_strm:
+ vocabulary = dill.load(in_strm)
+ with open(os.path.join(templates_dir, 'sentiment.data'), 'rb') as in_strm:
+ classifier = dill.load(in_strm)
+ text=set(word_tokenize(text))-set(stopwords.words('thai'))
+ featurized_test_sentence = {i:(i in text) for i in vocabulary}
+ return classifier.classify(featurized_test_sentence)
+ elif engine=='ulmfit':
+ from pythainlp.sentiment import ulmfit_sent
+ tag=ulmfit_sent.get_sentiment(text)
+ sa=""
+ if tag==0:
+ sa="neg"
+ else:
+ sa="pos"
+ return sa
+ else:
+ raise Exception("error no have engine.")
+if __name__ == '__main__':
+ d="เสียใจแย่มากเลย"
+ print(sentiment(d))
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,division,unicode_literals,print_function
+from builtins import *
+'''
+Thai soundex
+
+โค้ดพัฒนาโดย คุณ Korakot Chaovavanich (จาก https://gist.github.com/korakot/0b772e09340cac2f493868da035597e8)
+'''
+import re
+[docs]def LK82(s):
+ '''
+ LK82 - It's a thai soundex rule.
+
+ :param str s: thai word
+ :return: LK82 soundex
+ '''
+ t1 = str.maketrans("กขฃคฅฆงจฉชฌซศษสญยฎดฏตณนฐฑฒถทธบปผพภฝฟมรลฬฤฦวหฮอ","กกกกกกงจชชชซซซซยยดดตตนนททททททบปพพพฟฟมรรรรรวหหอ")
+ t2 = str.maketrans("กขฃคฅฆงจฉชซฌฎฏฐฑฒดตถทธศษสญณนรลฬฤฦบปพฟภผฝมำยวไใหฮาๅึืเแโุูอ","1111112333333333333333333444444445555555667777889AAABCDEEF")
+ res = []
+ s = re.sub("[่-๋]", "", s) # 4.ลบวรรณยุกต์
+ s = re.sub('จน์|มณ์|ณฑ์|ทร์|ตร์|[ก-ฮ]์|[ก-ฮ][ะ-ู]์', "", s) # 4.ลบตัวการันต์
+ s = re.sub("[็ํฺๆฯ]", "", s) # 5.ทิ้งไม้ไต่คู่ ฯลฯ
+ # 6.เข้ารหัสตัวแรก
+ if 'ก'<=s[0]<='ฮ':
+ res.append(s[0].translate(t1))
+ s = s[1:]
+ else:
+ res.append(s[1].translate(t1))
+ res.append(s[0].translate(t2))
+ s = s[2:]
+ # เข้ารหัสตัวที่เหลือ
+ i_v = None # ตำแหน่งตัวคั่นล่าสุด (สระ)
+ for i,c in enumerate(s):
+ if c in "ะัิี": # 7. ตัวคั่นเฉยๆ
+ i_v = i
+ res.append('')
+ elif c in "าๅึืู": # 8.คั่นและใส่
+ i_v = i
+ res.append(c.translate(t2))
+ elif c == 'ุ': # 9.สระอุ
+ i_v = i
+ if i==0 or (s[i-1] not in "ตธ"):
+ res.append(c.translate(t2))
+ else:
+ res.append('')
+ elif c in 'หอ':
+ if i+1<len(s) and (s[i+1] in "ึืุู"):
+ res.append(c.translate(t2))
+ elif c in 'รวยฤฦ':
+ if i_v == i-1 or (i+1<len(s) and (s[i+1] in "ึืุู")):
+ res.append(c.translate(t2))
+ else:
+ res.append(c.translate(t2)) # 12.
+ # 13. เอาตัวซ้ำออก
+ res2 = [res[0]]
+ for i in range(1, len(res)):
+ if res[i] != res[i-1]:
+ res2.append(res[i])
+ # 14. เติมศูนย์ให้ครบ ถ้าเกินก็ตัด
+ return ("".join(res2)+"0000")[:5]
+[docs]def Udom83(s):
+ '''
+ Udom83 - It's a thai soundex rule.
+
+ :param str s: thai word
+ :return: LK82 soundex
+ '''
+ tu1 = str.maketrans("กขฃคฅฆงจฉชฌซศษสฎดฏตฐฑฒถทธณนบปผพภฝฟมญยรลฬฤฦวอหฮ" ,"กขขขขขงจชชชสสสสดดตตททททททนนบปพพพฟฟมยยรรรรรวอฮฮ")
+ tu2 = str.maketrans("มวำกขฃคฅฆงยญณนฎฏดตศษสบปพภผฝฟหอฮจฉชซฌฐฑฒถทธรฤลฦ","0001111112233344444445555666666777778888889999")
+ s = re.sub('รร([เ-ไ])', 'ัน\\1', s) # 4.
+ s = re.sub('รร([ก-ฮ][ก-ฮเ-ไ])', 'ั\\1', s) # 5.
+ s = re.sub('รร([ก-ฮ][ะ-ู่-์])','ัน\\1', s)
+ s = re.sub('รร', 'ัน', s)
+ s = re.sub('ไ([ก-ฮ]ย)', '\\1', s) # 2.
+ s = re.sub('[ไใ]([ก-ฮ])','\\1ย', s)
+ s = re.sub('ำ(ม[ะ-ู])', 'ม\\1', s) # 3.
+ s = re.sub('ำม', 'ม', s)
+ s = re.sub('ำ', 'ม', s)
+ s = re.sub('จน์|มณ์|ณฑ์|ทร์|ตร์|[ก-ฮ]์|[ก-ฮ][ะ-ู]์', "", s) # 6.
+ s = re.sub('[ะ-์]', '', s) # 7.
+ sd = s[0].translate(tu1)
+ sd += s[1:].translate(tu2)
+ return (sd+'000000')[:7]
+if __name__ == '__main__':
+ print(LK82('รถ'))
+ print(LK82('รส'))
+ print(LK82('รด'))
+ print(LK82('จัน'))
+ print(LK82('จันทร์'))
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+[docs]def spell(word,engine='pn'):
+ """
+ :param str word: the word to check spelling
+ :param str engine:
+ * pn - Peter Norvig's algorithm
+ * hunspell - uses hunspell's algorithm, which should already exist in linux
+ :return: list word
+ """
+ if engine=='pn':
+ from .pn import spell as spell1
+ elif engine=='hunspell':
+ from .hunspell import spell as spell1
+ return spell1(word)
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+from pythainlp.corpus import stopwords
+from string import punctuation
+from collections import defaultdict
+from pythainlp.tokenize import sent_tokenize, word_tokenize
+from heapq import nlargest
+class FrequencySummarizer:
+ def __init__(self, min_cut=0.1, max_cut=0.9):
+ self._min_cut = min_cut
+ self._max_cut = max_cut
+ self._stopwords = set(stopwords.words('thai') + list(punctuation))
+
+ def _compute_frequencies(self, word_sent):
+ freq = defaultdict(int)
+ for s in word_sent:
+ for word in s:
+ if word not in self._stopwords:
+ freq[word] += 1
+ m = float(max(freq.values()))
+ for w in list(freq):
+ freq[w] = freq[w]/m
+ if freq[w] >= self._max_cut or freq[w] <= self._min_cut:
+ del freq[w]
+ return freq
+
+ def _rank(self, ranking, n):
+ return nlargest(n, ranking, key=ranking.get)
+
+ def summarize(self, text, n,tokenize):
+ sents = sent_tokenize(text)
+ word_sent = [word_tokenize(s,tokenize) for s in sents]
+ self._freq = self._compute_frequencies(word_sent)
+ ranking = defaultdict(int)
+ for i, sent in enumerate(word_sent):
+ for w in sent:
+ if w in self._freq:
+ ranking[i] += self._freq[w]
+ sents_idx = self._rank(ranking,n)
+ return [sents[j] for j in sents_idx]
+[docs]def summarize_text(text,n,engine='frequency',tokenize='newmm'):
+ '''
+ Thai text summarize.
+ :param str text: thai text
+ :param int n: sent number
+ :param str engine: Thai text summarize engine.
+ :param str tokenize: thai word tokenize.
+ '''
+ if engine=='frequency':
+ data=FrequencySummarizer().summarize(text,n,tokenize)
+ return data
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,division,print_function,unicode_literals
+import sys
+[docs]def pos_tag(list_text,engine='unigram',corpus='orchid'):
+ """
+ Part of Speech tagging function.
+
+ :param list list_text: takes in a list of tokenized words (put differently, a list of string)
+ :param str engine:
+ * unigram - unigram tagger
+ * perceptron - perceptron tagger
+ * artagger - RDR POS tagger
+ :param str corpus:
+ * orchid - annotated Thai academic articles
+ * pud - Parallel Universal Dependencies (PUD) treebanks
+ :return: returns a list of labels regarding which part of speech it is
+ """
+ if engine=='old' or engine=='unigram':
+ from .old import tag
+ elif engine=='perceptron':
+ from .perceptron import tag
+ elif engine=='artagger':
+ def tag(text1):
+ try:
+ from artagger import Tagger
+ except ImportError:
+ from pythainlp.tools import install_package
+ install_package('https://github.com/wannaphongcom/artagger/archive/master.zip')
+ try:
+ from artagger import Tagger
+ except ImportError:
+ print("Error ! using 'pip install https://github.com/wannaphongcom/artagger/archive/master.zip'")
+ sys.exit(0)
+ words = Tagger().tag(' '.join(text1))
+ totag=[]
+ for word in words:
+ totag.append((word.word, word.tag))
+ return totag
+ return tag(list_text)
+ return tag(list_text,corpus=corpus)
+
+def pos_tag_sents(sentences,engine='unigram',corpus='orchid'):
+ return [pos_tag(i,engine=engine,corpus=corpus) for i in sentences]
+
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import,unicode_literals
+import nltk
+import re
+import codecs
+from six.moves import zip
+from pythainlp.corpus.thaisyllable import get_data
+from pythainlp.corpus.thaiword import get_data as get_dict
+from marisa_trie import Trie
+
+DEFAULT_DICT_TRIE = Trie(get_dict())
+
+[docs]def word_tokenize(text, engine='newmm',whitespaces=True):
+ """
+ :param str text: the text to be tokenized
+ :param str engine: the engine to tokenize text
+ :param bool whitespaces: True to output no whitespace, a common mark of sentence or end of phrase in Thai.
+ :Parameters for engine:
+ * newmm - Maximum Matching algorithm + TCC
+ * icu - IBM ICU
+ * longest-matching - Longest matching
+ * mm - Maximum Matching algorithm
+ * pylexto - LexTo
+ * deepcut - Deep Neural Network
+ * wordcutpy - wordcutpy (https://github.com/veer66/wordcutpy)
+ :return: A list of words, tokenized from a text
+
+ **Example**::
+
+ from pythainlp.tokenize import word_tokenize
+ text='ผมรักคุณนะครับโอเคบ่พวกเราเป็นคนไทยรักภาษาไทยภาษาบ้านเกิด'
+ a=word_tokenize(text,engine='icu') # ['ผม', 'รัก', 'คุณ', 'นะ', 'ครับ', 'โอ', 'เค', 'บ่', 'พวก', 'เรา', 'เป็น', 'คน', 'ไทย', 'รัก', 'ภาษา', 'ไทย', 'ภาษา', 'บ้าน', 'เกิด']
+ b=word_tokenize(text,engine='dict') # ['ผม', 'รัก', 'คุณ', 'นะ', 'ครับ', 'โอเค', 'บ่', 'พวกเรา', 'เป็น', 'คนไทย', 'รัก', 'ภาษาไทย', 'ภาษา', 'บ้านเกิด']
+ c=word_tokenize(text,engine='mm') # ['ผม', 'รัก', 'คุณ', 'นะ', 'ครับ', 'โอเค', 'บ่', 'พวกเรา', 'เป็น', 'คนไทย', 'รัก', 'ภาษาไทย', 'ภาษา', 'บ้านเกิด']
+ d=word_tokenize(text,engine='pylexto') # ['ผม', 'รัก', 'คุณ', 'นะ', 'ครับ', 'โอเค', 'บ่', 'พวกเรา', 'เป็น', 'คนไทย', 'รัก', 'ภาษาไทย', 'ภาษา', 'บ้านเกิด']
+ e=word_tokenize(text,engine='newmm') # ['ผม', 'รัก', 'คุณ', 'นะ', 'ครับ', 'โอเค', 'บ่', 'พวกเรา', 'เป็น', 'คนไทย', 'รัก', 'ภาษาไทย', 'ภาษา', 'บ้านเกิด']
+ g=word_tokenize(text,engine='wordcutpy') # ['ผม', 'รัก', 'คุณ', 'นะ', 'ครับ', 'โอเค', 'บ่', 'พวกเรา', 'เป็น', 'คน', 'ไทย', 'รัก', 'ภาษา', 'ไทย', 'ภาษา', 'บ้านเกิด']
+ """
+ if engine=='icu':
+ from .pyicu import segment
+ elif engine=='multi_cut' or engine=='mm':
+ from .multi_cut import segment
+ elif engine=='newmm' or engine=='onecut':
+ from .newmm import mmcut as segment
+ elif engine=='longest-matching':
+ from .longest import segment
+ elif engine=='pylexto':
+ from .pylexto import segment
+ elif engine=='deepcut':
+ from .deepcut import segment
+ elif engine=='wordcutpy':
+ from .wordcutpy import segment
+ else:
+ raise Exception("error no have engine.")
+ if whitespaces==False:
+ return [i.strip(' ') for i in segment(text) if i.strip(' ')!='']
+ return segment(text)
+[docs]def dict_word_tokenize(text, custom_dict_trie, engine='newmm'):
+ '''
+ :meth:`dict_word_tokenize` tokenizes word based on the dictionary you provide. The format has to be in trie data structure.
+
+ :param str text: the text to be tokenized
+ :param dict custom_dict_trie: คือ trie ที่สร้างจาก create_custom_dict_trie
+ :param str engine: choose between different options of engine to token (newmm, wordcutpy, mm, longest-matching)
+ :return: A list of words, tokenized from a text.
+ **Example**::
+ >>> from pythainlp.tokenize import dict_word_tokenize,create_custom_dict_trie
+ >>> listword=['แมว',"ดี"]
+ >>> data_dict=create_custom_dict_trie(listword)
+ >>> dict_word_tokenize("แมวดีดีแมว",data_dict)
+ ['แมว', 'ดี', 'ดี', 'แมว']
+ '''
+ if engine=="newmm" or engine=="onecut":
+ from .newmm import mmcut as segment
+ elif engine=="mm" or engine=="multi_cut":
+ from .multi_cut import segment
+ elif engine=='longest-matching':
+ from .longest import segment
+ elif engine=='wordcutpy':
+ from .wordcutpy import segment
+ return segment(text, custom_dict_trie.keys())
+ else:
+ raise Exception("error no have engine.")
+ return segment(text, custom_dict_trie)
+[docs]def sent_tokenize(text,engine='whitespace+newline'):
+ '''
+ This function does not yet automatically recognize when a sentence actually ends. Rather it helps split text where white space and a new line is found.
+
+ :param str text: the text to be tokenized
+ :param str engine: choose between 'whitespace' or 'whitespace+newline'
+
+ :return: a list of text, split by whitespace or new line.
+ '''
+ if engine=='whitespace':
+ data=nltk.tokenize.WhitespaceTokenizer().tokenize(text)
+ elif engine=='whitespace+newline':
+ data=re.sub(r'\n+|\s+','|',text,re.U).split('|')
+ return data
+
+[docs]def subword_tokenize(text, engine='tcc'):
+ """
+ :param str text: text to be tokenized
+ :param str engine: choosing 'tcc' uses the Thai Character Cluster rule to segment words into the smallest unique units.
+ :return: a list of tokenized strings.
+ """
+ if engine == 'tcc':
+ from .tcc import tcc
+ return tcc(text)
+
+[docs]def isthai(text,check_all=False):
+ """
+ :param str text: input string or list of strings
+ :param bool check_all: checks all character or not
+
+ :return: A dictionary with the first value as proportional of text that is Thai, and the second value being a tuple of all characters, along with true or false.
+ """
+ listext=list(text)
+ i=0
+ num_isthai=0
+ if check_all==True:
+ listthai=[]
+ while i<len(listext):
+ cVal = ord(listext[i])
+ if(cVal >= 3584 and cVal <= 3711):
+ num_isthai+=1
+ if check_all==True:
+ listthai.append(True)
+ else:
+ if check_all==True:
+ listthai.append(False)
+ i+=1
+ thai=(num_isthai/len(listext))*100
+ if check_all==True:
+ dictthai=tuple(zip(listext,listthai))
+ data= {'thai':thai,'check_all':dictthai}
+ else:
+ data= {'thai':thai}
+ return data
+
+def syllable_tokenize(text):
+ """
+ :param str text: input string to be tokenized
+
+ :return: returns list of strings of syllables
+ """
+ text1=word_tokenize(text)
+ data=[]
+ trie = create_custom_dict_trie(custom_dict_source=get_data())
+ if len(text1)>1:
+ i=0
+ while i<len(text1):
+ data.extend(dict_word_tokenize(text=text1[i], custom_dict_trie=trie))
+ i+=1
+ else:
+ data=dict_word_tokenize(text=text, custom_dict_trie=trie)
+ return data
+
+[docs]def create_custom_dict_trie(custom_dict_source):
+ """The function is used to create a custom dict trie which will be used for word_tokenize() function. For more information on the trie data structure, see: https://marisa-trie.readthedocs.io/en/latest/index.html
+
+ :param string/list custom_dict_source: a list of vocaburaries or a path to source file
+
+ :return: A trie created from custom dict input
+ """
+
+ if type(custom_dict_source) is str:
+ # Receive a file path of the custom dict to read
+ with codecs.open(custom_dict_source, 'r',encoding='utf8') as f:
+ _vocabs = f.read().splitlines()
+ return Trie(_vocabs)
+ elif isinstance(custom_dict_source, (list, tuple, set)):
+ # Received a sequence type object of vocabs
+ return Trie(custom_dict_source)
+ else:
+ raise TypeError(
+ 'Type of custom_dict_source must be either str (path to source file) or collections'
+ )
+
+class Tokenizer:
+ def __init__(self, custom_dict=None):
+ """
+ Initialize tokenizer object
+
+ :param str custom_dict: a file path or a list of vocaburaies to be used to create a trie (default - original lexitron)
+
+ :return: trie_dict - a dictionary in the form of trie data for tokenizing engines
+ """
+ if custom_dict:
+ if type(custom_dict) is list:
+ self.trie_dict = Trie(custom_dict)
+ elif type(custom_dict) is str:
+ with codecs.open(custom_dict, 'r',encoding='utf8') as f:
+ vocabs = f.read().splitlines()
+ self.trie_dict = Trie(vocabs)
+ else:
+ self.trie_dict = Trie(get_dict())
+
+ def word_tokenize(self, text, engine='newmm'):
+ from .newmm import mmcut as segment
+ return segment(text, self.trie_dict)
+
+
+# -*- coding: utf-8 -*-
+'''
+Code by https://github.com/cstorm125/thai2vec/tree/master/notebook
+'''
+from __future__ import absolute_import,unicode_literals
+import os
+import sys
+import re
+import torch
+
+#numpy and fastai
+try:
+ import numpy as np
+ from fastai.text import *
+ import dill as pickle
+except ImportError:
+ from pythainlp.tools import install_package
+ install_package('fastai')
+ install_package('numpy')
+ try:
+ import numpy as np
+ from fastai.text import *
+ import dill as pickle
+ except ImportError:
+ print("Error installing using 'pip install fastai numpy dill'")
+ sys.exit(0)
+
+#import torch
+try:
+ import torch
+except ImportError:
+ print('PyTorch required. See https://pytorch.org/.')
+
+from pythainlp.tokenize import word_tokenize
+from pythainlp.corpus import get_file
+from pythainlp.corpus import download
+MODEL_NAME = 'thwiki_model2'
+ITOS_NAME = 'itos'
+
+#paralellized thai tokenizer with some text cleaning
+[docs]class ThaiTokenizer():
+ def __init__(self, engine='newmm'):
+ """
+ :parameters for tokenization engine:
+ * newmm - Maximum Matching algorithm + TCC
+ * icu - IBM ICU
+ * longest-matching - Longest matching
+ * mm - Maximum Matching algorithm
+ * pylexto - LexTo
+ * deepcut - Deep Neural Network
+ """
+ self.engine = engine
+ self.re_br = re.compile(r'<\s*br\s*/?>', re.IGNORECASE)
+ self.re_rep = re.compile(r'(\S)(\1{3,})')
+
+[docs] def sub_br(self,text):
+ """
+ :meth:`sub_br` replace `<br>` tags with `\n`
+ :param str text: text to process
+ :return: procssed text
+ """
+ return self.re_br.sub("\n", text)
+
+[docs] def tokenize(self,text):
+ """
+ :meth: tokenize text with selected engine
+ :param str text: text to tokenize
+ :return: tokenized text
+ """
+ return [t for t in word_tokenize(self.sub_br(text),engine=self.engine)]
+
+[docs] @staticmethod
+ def replace_rep(text):
+ '''
+ :meth:`replace_rep` replace 3 or above repetitive characters with `tkrep`
+ :param str text: text to process
+ :return: processed text where repetitions are replaced by `tkrep` followed by number of repetitions
+ **Example**::
+ >>> from pythainlp.ulmfit.utils import ThaiTokenizer
+ >>> tt = ThaiTokenizer()
+ >>> tt.replace_rep('คือดียยยยยย')
+ คือดีtkrep6ย
+ '''
+ TK_REP = 'tkrep'
+ c,cc = text.groups()
+ return f'{TK_REP}{len(cc)+1}{c}'
+
+[docs] def proc_text(self, text):
+ """
+ :meth: `proc_text` procss and tokenize text removing repetitions, special characters, double spaces
+ :param str text: text to process
+ :return: processed and tokenized text
+ """
+ s = self.re_rep.sub(ThaiTokenizer.replace_rep, text)
+ s = re.sub(r'([/#])', r' \1 ', s)
+ #remvoe double space
+ s = re.sub(' {2,}', ' ', s)
+ return self.tokenize(s)
+
+[docs] @staticmethod
+ def proc_all(ss):
+ """
+ :meth: `proc_all` runs `proc_text` for multiple sentences
+ :param str text: text to process
+ :return: processed and tokenized text
+ """
+ tok = ThaiTokenizer()
+ return [tok.proc_text(s) for s in ss]
+
+[docs] @staticmethod
+ def proc_all_mp(ss):
+ """
+ :meth: `proc_all` runs `proc_text` for multiple sentences using multiple cpus
+ :param str text: text to process
+ :return: processed and tokenized text
+ """
+ ncpus = num_cpus()//2
+ with ProcessPoolExecutor(ncpus) as e:
+ return sum(e.map(ThaiTokenizer.proc_all, ss), [])
+
+#ulmfit helper functions
+BOS = 'xbos' # beginning-of-sentence tag
+[docs]def get_texts(df):
+ """
+ :meth: `get_texts` get tuple of tokenized texts and labels
+ :param pandas.DataFrame df: `pandas.DataFrame` with `label` as first column and `text` as second column
+ :return:
+ * tok - lists of tokenized texts with beginning-of-sentence tag `xbos` as first element of each list
+ * labels - list of labels
+ """
+ labels = df.iloc[:,0].values.astype(np.int64)
+ texts = BOS+df.iloc[:,1].astype(str).apply(lambda x: x.rstrip())
+ tok = ThaiTokenizer().proc_all_mp(partition_by_cores(texts))
+ return(tok, list(labels))
+
+[docs]def get_all(df):
+ """
+ :meth: `get_all` iterate `get_texts` for all the entire `pandas.DataFrame`
+ :param pandas.DataFrame df: `pandas.DataFrame` with `label` as first column and `text` as second column
+ :return:
+ * tok - lists of tokenized texts with beginning-of-sentence tag `xbos` as first element of each list
+ * labels - list of labels
+ """
+ tok, labels = [], []
+ for i, r in enumerate(df):
+ tok_, labels_ = get_texts(r)
+ tok += tok_;
+ labels += labels_
+ return(tok, labels)
+
+[docs]def numericalizer(df, itos=None, max_vocab = 60000, min_freq = 2, pad_tok = '_pad_', unk_tok = '_unk_'):
+ """
+ :meth: `numericalize` numericalize tokenized texts for:
+ * tokens with word frequency more than `min_freq`
+ * at maximum vocab size of `max_vocab`
+ * add unknown token `_unk_` and padding token `_pad_` in first and second position
+ * use integer-to-string list `itos` if avaiable e.g. ['_unk_', '_pad_','first_word','second_word',...]
+ :param pandas.DataFrame df: `pandas.DataFrame` with `label` as first column and `text` as second column
+ :param list itos: integer-to-string list
+ :param int max_vocab: maximum number of vocabulary (default 60000)
+ :param int min_freq: minimum word frequency to be included (default 2)
+ :param str pad_tok: padding token
+ :param str unk_token: unknown token
+ :return:
+ * lm - `numpy.array` of numericalized texts
+ * tok - lists of tokenized texts with beginning-of-sentence tag `xbos` as first element of each list
+ * labels - list of labels
+ * itos - integer-to-string list e.g. ['_unk_', '_pad_','first_word','second_word',...]
+ * stoi - string-to-integer dict e.g. {'_unk_':0, '_pad_':1,'first_word':2,'second_word':3,...}
+ * freq - `collections.Counter` for word frequency
+ """
+ tok, labels = get_all(df)
+ freq = Counter(p for o in tok for p in o)
+ if itos is None:
+ itos = [o for o,c in freq.most_common(max_vocab) if c>min_freq]
+ itos.insert(0, pad_tok)
+ itos.insert(0, unk_tok)
+ stoi = collections.defaultdict(lambda:0, {v:k for k,v in enumerate(itos)})
+ lm = np.array([[stoi[o] for o in p] for p in tok])
+ return(lm,tok,labels,itos,stoi,freq)
+
+[docs]def merge_wgts(em_sz, wgts, itos_pre, itos_cls):
+ """
+ :param pandas.DataFrame df: `pandas.DataFrame` with `label` as first column and `text` as second column
+ :param int em_sz: size of embedding vectors (pretrained model is at 300)
+ :param wgts: saved pyTorch weights of pretrained model
+ :param list itos_pre: integer-to-string list of pretrained model
+ :param list itos_cls: integer-to-string list of current dataset
+ :return: merged weights of the model for current dataset
+ """
+ vocab_size = len(itos_cls)
+ enc_wgts = to_np(wgts['0.encoder.weight'])
+ #average weight of encoding
+ row_m = enc_wgts.mean(0)
+ stoi_pre = collections.defaultdict(lambda:-1, {v:k for k,v in enumerate(itos_pre)})
+ #new embedding based on classification dataset
+ new_w = np.zeros((vocab_size, em_sz), dtype=np.float32)
+ for i,w in enumerate(itos_cls):
+ r = stoi_pre[w]
+ #use pretrianed embedding if present; else use the average
+ new_w[i] = enc_wgts[r] if r>=0 else row_m
+ wgts['0.encoder.weight'] = T(new_w)
+ wgts['0.encoder_with_dropout.embed.weight'] = T(np.copy(new_w))
+ wgts['1.decoder.weight'] = T(np.copy(new_w))
+ return(wgts)
+
+#feature extractor
+[docs]def document_vector(ss, m, stoi,tok_engine='newmm'):
+ """
+ :meth: `document_vector` get document vector using pretrained ULMFit model
+ :param str ss: sentence to extract embeddings
+ :param m: pyTorch model
+ :param dict stoi: string-to-integer dict e.g. {'_unk_':0, '_pad_':1,'first_word':2,'second_word':3,...}
+ :param str tok_engine: tokenization engine (recommend using `newmm` if you are using pretrained ULMFit model)
+ :return: `numpy.array` of document vector sized 300
+ """
+ s = word_tokenize(ss)
+ t = LongTensor([stoi[i] for i in s]).view(-1,1).cuda()
+ t = Variable(t,volatile=False)
+ m.reset()
+ pred,*_ = m[0](t)
+ #get average of last lstm layer along bptt
+ res = to_np(torch.mean(pred[-1],0).view(-1))
+ return(res)
+
+class SaveFeatures():
+ features=None
+ def __init__(self, m): self.hook = m.register_forward_hook(self.hook_fn)
+ def hook_fn(self, module, input, output): self.features = output
+ def remove(self): self.hook.remove()
+
+#Download pretrained models
+def get_path(fname):
+ path = get_file(fname)
+ if path==None:
+ download(fname)
+ path = get_file(fname)
+ return(path)
+
+def load_pretrained_model():
+ path = get_path(MODEL_NAME)
+ wgts = torch.load(path, map_location=lambda storage, loc: storage)
+ return(wgts)
+
+def load_pretrained_itos():
+ path = get_path(ITOS_NAME)
+ itos = pickle.load(open(path,'rb'))
+ return(itos)
+
+[docs]def about():
+ return '''
+ thai2vec
+ State-of-the-Art Language Modeling, Text Feature Extraction and Text Classification in Thai Language.
+ Created as part of pyThaiNLP with ULMFit implementation from fast.ai
+
+ Development : Charin Polpanumas
+ GitHub : https://github.com/cstorm125/thai2vec
+ '''
+
+# -*- coding: utf-8 -*-
+'''
+Code by https://github.com/cstorm125/thai2vec/blob/master/notebooks/examples.ipynb
+'''
+from __future__ import absolute_import,unicode_literals
+import six
+import sys
+if six.PY2:
+ print("Thai sentiment in pythainlp. Not support python 2.7")
+ sys.exit(0)
+try:
+ from gensim.models import KeyedVectors
+ import numpy as np
+except ImportError:
+ from pythainlp.tools import install_package
+ install_package('gensim')
+ install_package('numpy')
+ try:
+ from gensim.models import KeyedVectors
+ import numpy as np
+ except ImportError:
+ print("Error ! using 'pip install gensim numpy'")
+ sys.exit(0)
+from pythainlp.tokenize import word_tokenize
+from pythainlp.corpus import get_file
+from pythainlp.corpus import download as download_data
+import os
+
+def download():
+ path = get_file('thai2vec02')
+ if path==None:
+ download_data('thai2vec02')
+ path = get_file('thai2vec02')
+ return path
+[docs]def get_model():
+ '''
+ :return: Downloads the `gensim` model.'''
+ return KeyedVectors.load_word2vec_format(download(),binary=False)
+[docs]def most_similar_cosmul(positive,negative):
+ '''
+ การใช้งาน
+ input list
+ '''
+ return get_model().most_similar_cosmul(positive=positive, negative=negative)
+
+[docs]def similarity(word1,word2):
+ '''
+ :param str word1: first word
+ :param str word2: second word
+ :return: the cosine similarity between the two word vectors
+ '''
+ return get_model().similarity(word1,word2)
+[docs]def sentence_vectorizer(ss,dim=300,use_mean=False):
+ s = word_tokenize(ss)
+ vec = np.zeros((1,dim))
+ for word in s:
+ if word in get_model().wv.index2word:
+ vec+= get_model().wv.word_vec(word)
+ else: pass
+ if use_mean: vec /= len(s)
+ return(vec)
+
+[docs]def about():
+ return '''
+ thai2vec
+ State-of-the-Art Language Modeling, Text Feature Extraction and Text Classification in Thai Language.
+ Created as part of pyThaiNLP with ULMFit implementation from fast.ai
+
+ Development : Charin Polpanumas
+ GitHub : https://github.com/cstorm125/thai2vec
+ '''
+
' + _('Hide Search Matches') + '
') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js new file mode 100644 index 000000000..4e68537a7 --- /dev/null +++ b/docs/_build/html/_static/documentation_options.js @@ -0,0 +1,9 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '1.7', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt' +}; \ No newline at end of file diff --git a/docs/_build/html/_static/down-pressed.png b/docs/_build/html/_static/down-pressed.png new file mode 100644 index 000000000..5756c8cad Binary files /dev/null and b/docs/_build/html/_static/down-pressed.png differ diff --git a/docs/_build/html/_static/down.png b/docs/_build/html/_static/down.png new file mode 100644 index 000000000..1b3bdad2c Binary files /dev/null and b/docs/_build/html/_static/down.png differ diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/docs/_build/html/_static/file.png differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.eot b/docs/_build/html/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 000000000..3361183a4 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.ttf b/docs/_build/html/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 000000000..29f691d5e Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.woff b/docs/_build/html/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 000000000..c6dff51f0 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 b/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 000000000..bb195043c Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 000000000..3d4154936 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 000000000..f402040b3 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 000000000..88ad05b9f Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 000000000..c4e3d804b Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.eot b/docs/_build/html/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 000000000..3f826421a Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.ttf b/docs/_build/html/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 000000000..b4bfc9b24 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.woff b/docs/_build/html/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 000000000..76114bc03 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 b/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 000000000..3404f37e2 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.eot b/docs/_build/html/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 000000000..11e3f2a5f Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.ttf b/docs/_build/html/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 000000000..74decd9eb Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.woff b/docs/_build/html/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 000000000..ae1307ff5 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 b/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 000000000..3bf984332 Binary files /dev/null and b/docs/_build/html/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 000000000..79dc8efed Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 000000000..df5d1df27 Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 000000000..6cb600001 Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 000000000..7059e2314 Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 000000000..2f7ca78a1 Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 000000000..eb52a7907 Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 000000000..f815f63f9 Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 000000000..f2c76e5bd Binary files /dev/null and b/docs/_build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.eot b/docs/_build/html/_static/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..e9f60ca95 Binary files /dev/null and b/docs/_build/html/_static/fonts/fontawesome-webfont.eot differ diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.svg b/docs/_build/html/_static/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..855c845e5 --- /dev/null +++ b/docs/_build/html/_static/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.ttf b/docs/_build/html/_static/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/docs/_build/html/_static/fonts/fontawesome-webfont.ttf differ diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/docs/_build/html/_static/fonts/fontawesome-webfont.woff differ diff --git a/docs/_build/html/_static/fonts/fontawesome-webfont.woff2 b/docs/_build/html/_static/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..4d13fc604 Binary files /dev/null and b/docs/_build/html/_static/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/_build/html/_static/jquery-3.2.1.js b/docs/_build/html/_static/jquery-3.2.1.js new file mode 100644 index 000000000..d2d8ca479 --- /dev/null +++ b/docs/_build/html/_static/jquery-3.2.1.js @@ -0,0 +1,10253 @@ +/*! + * jQuery JavaScript Library v3.2.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2017-03-20T18:59Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.2.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && Array.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + !compilerCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: