try:
    from lxml import etree
except:
    pass

import re
import os
import sys
import json
import shutil
import pprint
import traceback
import threading
import tkinter
import tkinter.messagebox
from tkinter import ttk
from tkinter.simpledialog import askstring

from .root import (
    root,
    config,
    save,
    home,
)
from .frame import (
    request_window,
    response_window,
    code_window,
    scrapy_code_window,
    helper_window,
    frame_setting,
    exec_js_window,
    selenium_test_window,
    encode_window,
    __org_stdout__,
)
from .util import (
    dprint,
    format_url,
    format_url_show,
    format_url_code,
    format_headers_str,
    format_headers_code,
    format_body_str,
    format_body_code,
    format_request,
    format_request_urllib,
    format_response,
    format_scrapy_request,
    format_scrapy_response,
    get_simple_path_tail,
    get_xpath_by_str,
    get_json_show,
    parse_json_content,
)

nb = ttk.Notebook(root)
nb.place(relx=0, rely=0, relwidth=1, relheight=1)
nb_names = {} 
'''
nb_names 的数据结构:
{
    tab_id1: 
        {
            'name':tab_name1,
            'setting':setting1,
        }, 
    tab_id2:
        {
            'name':tab_name2,
            'setting':setting2,
        }, 
}
setting 是一个字典,里面至少有一个 type字段描述什么类型。
'''


def bind_frame(frame, name=None):
    global config, nb_names
    frame.master = nb
    name = name if name is not None else frame._name
    v = set(nb.tabs())
    nb.add(frame, text=name)
    tab_id = (set(nb.tabs())^v).pop() # 由于没有接口,只能用这种方式来获取新增的 tab_id
    nb_names[tab_id] = {}
    nb_names[tab_id]['name'] = name
    nb_names[tab_id]['setting'] = frame_setting.get(frame)# frame_setting.pop(frame) if frame in frame_setting else {}
    # 本来还想着在这里清理 frame_setting 内关闭的窗口,但是实际上这个工具暂时不会开太多窗口
    # 并且为了在某个地方优化一下请求时的卡顿,所以这里不能在窗口创建的时候就清除转移里面的数据,否则就无法优化。
    return tab_id


cancel_limit = 30
cancel_list = []

def delete_curr_tab():
    _select = nb.select()
    cname = nb_names.get(_select)['name']
    if _select is not '':
        if len(nb.tabs()) == 1 and cname == '帮助':
            root.destroy()
        elif len(nb.tabs()) == 1:
            nb.forget(_select)
            create_helper()
        else:
            nb.forget(_select)
        nb_names.pop(_select)
        if cname in config['set']:
            _set = config['set'].pop(cname)
            cancel_list.append((cname,_set))
            if len(cancel_list) > cancel_limit:
                cancel_list.pop(0)


def cancel_delete():
    if cancel_list:
        key,setting = cancel_list.pop()
        tab_id = bind_frame(request_window(setting),key)
        config['set'][key] = setting
        nb.select(tab_id)


def change_tab_name():
    name = askstring('修改标签','新的标签名字') # 简单弹窗请求字符串数据
    if name is not None:
        _select = nb.select()
        oname = nb_names[_select]['name']
        cname = name
        allname = [val['name'] for val in nb_names.values()]
        idx = 0
        while True:
            if cname in allname:
                idx += 1
                cname = '{}_{}'.format(name,idx)
            else:
                break
        # name不能重复,因为需要作为字典的key持久化
        nb_names[_select]['name'] = cname
        if oname in config['set']:
            config['set'][cname] = config['set'].pop(oname)
        nb.tab(_select, text=cname)



def create_new_tab(setting=None, prefix=None, window=None):
    nums = []
    for val in nb_names.values():
        v = re.findall('{}\d+'.format(prefix), val['name'])
        if val['name'] == prefix:
            nums.append(0)
        if v:
            num = int(re.findall('{}(\d+)'.format(prefix), v[0])[0])
            nums.append(num)
    idx = 0
    while True:
        if idx in nums:
            idx += 1
        else:
            retn = idx
            break
    name = '{}{}'.format(prefix, '' if retn==0 else retn)
    nb.select(bind_frame(window(setting),name))


def create_new_reqtab(setting=None, prefix='请求'):
    create_new_tab(setting, prefix, request_window)


def create_new_rsptab(setting=None, prefix='响应', reqname=None):
    fmt = '<{}>'
    prefix = prefix+fmt.format('空') if reqname is None else prefix+fmt.format(reqname)
    create_new_tab(setting, prefix, response_window)

def create_new_codetab(setting=None, prefix='代码', reqname=None):
    fmt = '<{}>'
    prefix = prefix+fmt.format('空') if reqname is None else prefix+fmt.format(reqname)
    create_new_tab(setting, prefix, code_window)

def create_new_scrapy_codetab(setting=None, prefix='scrapy', reqname=None):
    fmt = '<{}>'
    prefix = prefix+fmt.format('空') if reqname is None else prefix+fmt.format(reqname)
    create_new_tab(setting, prefix, scrapy_code_window)

def create_helper():
    nb.select(bind_frame(helper_window(),'帮助'))

def create_js_parse(setting=None, prefix='js执行窗'):
    create_new_tab(setting, prefix, exec_js_window)

def create_selenium_parse(setting=None, prefix='浏览器启动'):
    create_new_tab(setting, prefix, selenium_test_window)

def create_encoder(setting=None):
    encode_window()

def set_request_config(name,setting):
    method  = setting.get('fr_method').get()
    url     = setting.get('fr_url').get(0.,tkinter.END).strip()
    url     = format_url(url)
    headers = setting.get('fr_headers').get(0.,tkinter.END).strip()
    body    = setting.get('fr_body').get(0.,tkinter.END).strip()
    urlenc  = setting.get('fr_urlenc').get()
    qplus   = setting.get('fr_qplus').get()
    va,prox = setting.get('fr_proxy')
    proxy   = prox.get().strip() if va.get() else None
    config['set'][name] = {}
    config['set'][name]['type'] = 'request'
    config['set'][name]['method'] = method
    config['set'][name]['url'] = url
    config['set'][name]['headers'] = headers
    config['set'][name]['body'] = body
    config['set'][name]['urlenc'] = urlenc
    config['set'][name]['qplus'] = qplus
    config['set'][name]['proxy'] = proxy
    headers = format_headers_str(headers)
    body    = format_body_str(body)
    setting.get('fr_url').delete(0.,tkinter.END)
    setting.get('fr_url').insert(0.,format_url_show(url, urlenc))
    return method,url,headers,body,urlenc,qplus,proxy



def get_request_config(setting):
    method  = setting.get('fr_method').get()
    url     = setting.get('fr_url').get(0.,tkinter.END).strip()
    url     = format_url(url)
    headers = setting.get('fr_headers').get(0.,tkinter.END).strip()
    body    = setting.get('fr_body').get(0.,tkinter.END).strip()
    urlenc  = setting.get('fr_urlenc').get()
    qplus   = setting.get('fr_qplus').get()
    c_headers = format_headers_code(headers)
    c_url = format_url_code(url, urlenc)
    c_body = format_body_code(body)
    return method,c_url,c_headers,c_body,urlenc,qplus




def get_response_config(setting):
    tx1 = setting.get('fr_html_content')
    tx2 = setting.get('fr_local_set')
    tx3 = setting.get('fr_local_info')
    tx4 = setting.get('fr_parse_info')
    tps = setting.get('fr_parse_type')
    temp_fr2 = setting.get('fr_temp2')

    c_content = tx1.get(0.,tkinter.END).strip() # 配置,目前在解析json数据部分有用
    c_set     = tx2.get(0.,tkinter.END).strip() # 配置,用来配置生成代码的方式
    urlenc    = setting.get('fr_urlenc').get()
    qplus     = setting.get('fr_qplus').get()
    extra     = setting.get('fr_extra')

    r_setting = setting.get('fr_setting')
    if r_setting is not None:
        method    = r_setting.get('method')
        url       = r_setting.get('url')
        headers   = r_setting.get('headers')
        body      = r_setting.get('body')
        c_url     = format_url_code(url, urlenc)
        c_headers = format_headers_code(headers)
        c_body    = format_body_code(body) if type(body) == dict else 'body = ' + json.dumps(body)
        r_setting = method,c_url,c_headers,c_body
    return r_setting,c_set,c_content,tps,urlenc,qplus,extra




@save
def send_request():
    global config
    _select = nb.select()
    name    = nb_names[_select]['name']
    setting = nb_names[_select]['setting']
    foc_tog = True
    if setting.get('type') == 'request':
        method,url,headers,body,urlenc,qplus,proxy = set_request_config(name,setting)
        _setting = {}
        _setting['method'] = method
        _setting['url'] = url
        _setting['headers'] = headers
        _setting['body'] = body
        _setting['urlenc'] = urlenc
        _setting['qplus'] = qplus
        _setting['proxy'] = proxy
        create_new_rsptab(_setting,reqname=name)
    else:
        foc_tog = False
    if foc_tog:
        config['focus'] = name



def save_config():
    toggle = tkinter.messagebox.askokcancel('是否保存','确定保存当前全部[请求配置]信息吗?')
    @save
    def _save_config():
        for tad_id in nb.tabs():
            name = nb_names[tad_id]['name']
            setting = nb_names[tad_id]['setting']
            if setting.get('type') == 'request':
                set_request_config(name,setting)
    if toggle: _save_config()


# 切换状态:显示/取消显示 response 窗口内的输出窗口
def switch_response_log(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') in ['response','code','js','scrapy','selenium']:
        temp_fr2 = setting.get('fr_temp2')
        try:
            temp_fr2.pack_info()
            packed = True
        except:
            packed = False
        if packed:
            temp_fr2.pack_forget()
        else:
            temp_fr2.pack(fill=tkinter.BOTH,expand=True,side=tkinter.BOTTOM)



# 生成代码的函数
def create_test_code(*a):
    _select = nb.select()
    name    = nb_names[_select]['name']
    setting = nb_names[_select]['setting']
    code_string = None
    if setting.get('type') == 'request':
        method,c_url,c_headers,c_body,urlenc,qplus = get_request_config(setting)
        code_string = format_request(method,c_url,c_headers,c_body,urlenc,qplus)
    if setting.get('type') == 'response':
        r_setting,c_set,c_content,tps,urlenc,qplus,extra = get_response_config(setting)
        code_string = format_response(r_setting,c_set,c_content,urlenc,qplus,extra)
    if setting.get('type') == 'js':
        setting.get('execute_func0')()
    if setting.get('type') == 'selenium':
        setting.get('start_selenium')()
    if code_string:
        setting['code_string'] = code_string
        create_new_codetab(setting,reqname=name)



def create_test_code_urllib(*a):
    _select = nb.select()
    name    = nb_names[_select]['name']
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'request':
        method,c_url,c_headers,c_body,urlenc,qplus = get_request_config(setting)
        code_string = format_request_urllib(method,c_url,c_headers,c_body,urlenc,qplus)
        setting['code_string'] = code_string
        create_new_codetab(setting,reqname=name)
    if setting.get('type') == 'response':
        (method,c_url,c_headers,c_body),c_set,c_content,tps,urlenc,qplus,extra = get_response_config(setting)
        code_string = format_request_urllib(method,c_url,c_headers,c_body,urlenc,qplus,extra)
        setting['code_string'] = code_string
        create_new_codetab(setting,reqname=name)



# 生成 scrapy 代码的函数 # 暂时还在开发中
def create_scrapy_code(*a):
    '''
    这里的处理可能会需要花上挺长的一段时间,需要考虑新的code_window的处理
    等明天有时间的时候去处理一下
    '''
    _select = nb.select()
    name    = nb_names[_select]['name']
    setting = nb_names[_select]['setting']
    home = os.environ.get('HOME')
    home = home if home else os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH')
    model_path = os.path.join(os.path.split(__file__)[0],'template')
    filename = '.vrequest_scrapy'
    scrapypath = os.path.join(home,filename)
    scriptpath = os.path.join(scrapypath, 'v/spiders/')
    if not os.path.isdir(scrapypath):
        shutil.copytree(model_path, scrapypath)
    elif not os.path.isdir(os.path.join(scrapypath,'v')):
        toggle = tkinter.messagebox.askokcancel('是否删除已有的、错误的配置文件夹',
                                                '存在错误的配置文件夹 {} 确定删除该文件夹内容重新创建吗?\n'
                                                '配置文件夹地址 {}'.format(filename, scrapypath))
        if toggle:
            shutil.rmtree(scrapypath)
            shutil.copytree(model_path, scrapypath)
        else: return
    code_string = None
    if setting.get('type') == 'request':
        method,c_url,c_headers,c_body,urlenc,qplus = get_request_config(setting)
        code_string = format_scrapy_request(method,c_url,c_headers,c_body,urlenc,qplus)
    if setting.get('type') == 'response':
        r_setting,c_set,c_content,tps,urlenc,qplus,extra = get_response_config(setting)
        code_string = format_scrapy_response(r_setting,c_set,c_content,tps,urlenc,qplus,extra)
    if code_string:
        setting['code_string'] = code_string
        create_new_scrapy_codetab(setting,reqname=name)



# 获取HTML纯文本函数
def normal_content(content,
                   tags=['script','style','select','noscript'],
                   rootxpath='//html'):
    if type(content) is bytes:
        try:
            c = content.decode('utf-8')
        except:
            c = content.decode('gbk')
    elif type(content) is str:
        c = content
    else:
        raise TypeError('content type must in [bytes, str].')
    c = re.sub('>([^>]*[\u4e00-\u9fa5]{1,}[^<]*)<','>\g<1> <',c)
    e = etree.HTML(c)
    q = []
    for it in e.getiterator():
        if it.tag in tags or type(it.tag) is not str:
            q.append(it)
    for it in q:
        p = it.getparent()
        if p is not None:
            p.remove(it)
    t = e.xpath('normalize-space({})'.format(rootxpath))
    return t.strip()

# 显示 response 窗口内的输出窗口
def show_response_log():
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        setting.get('fr_temp2').pack(fill=tkinter.BOTH,expand=True,side=tkinter.BOTTOM)

# 获取内部文本的函数
def get_html_pure_text(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        txt = setting.get('fr_html_content')
        tx2 = setting.get('fr_local_set')

        c_set = tx2.get(0.,tkinter.END).strip()
        rt = '//html'
        toggle = True
        for i in c_set.splitlines():
            i = i.strip()
            if i.startswith('<') and i.endswith('>'):
                if i.startswith('<normal_content:'):
                    rt = re.findall('<normal_content:(.*)>', i)[0].strip()
                    toggle = False

        tx4 = setting.get('fr_parse_info')
        try:
            content = normal_content(txt.get(0.,tkinter.END), rootxpath=rt)
        except:
            content = traceback.format_exc()
        tx4.delete(0.,tkinter.END)
        tx4.insert(0.,content)
        if toggle:
            tx2.delete(0.,tkinter.END)
            tx2.insert(0.,'<normal_content://html>')
            
        show_response_log()




# 显示 代码 窗口内的输出窗口
def show_code_log():
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'code' or setting.get('type') == 'js' or setting.get('type') == 'scrapy' or setting.get('type') == 'selenium':
        setting.get('fr_temp2').pack(fill=tkinter.BOTH,expand=True,side=tkinter.BOTTOM)

def execute_code(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'code' or setting.get('type') == 'js' or setting.get('type') == 'scrapy' or setting.get('type') == 'selenium':
        execute_func = setting.get('execute_func')
        show_code_log()
        execute_func()

def execute_scrapy_code(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'scrapy':
        execute_func = setting.get('execute_func_console')
        execute_func()


from tkinter import (
    Toplevel,
    Message,
    LEFT,
    BOTH,
    TOP,
    RIDGE,
    Frame,
    Button,
)
import tkinter.ttk as ttk
class SimpleDialog:
    def __init__(self, master,
                 text='', buttons=[], default=None, cancel=None,
                 title=None, class_=None):
        if class_:
            self.root = Toplevel(master, class_=class_)
        else:
            self.root = Toplevel(master)
        if title:
            self.root.title(title)
            self.root.iconname(title)
        self.message = Message(self.root, text=text, aspect=400)
        self.message.pack(expand=1, fill=BOTH)
        self.frame = Frame(self.root)
        self.frame.pack()
        self.num = default
        self.cancel = cancel
        self.default = default
        self.root.bind('<Return>', self.return_event)
        self.root.bind('<Escape>', self.wm_delete_window)
        for num in range(len(buttons)):
            s = buttons[num]
            f = Frame(self.frame)
            b = ttk.Button(f, text=s,
                       command=(lambda self=self, num=num: self.done(num)))
            b.pack(side=LEFT, fill=BOTH)
            f.pack(side=TOP, fill=BOTH)
        self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
        self._set_transient(master)

    def _set_transient(self, master, relx=0.5, rely=0.3):
        widget = self.root
        widget.withdraw() # Remain invisible while we figure out the geometry
        widget.transient(master)
        widget.update_idletasks() # Actualize geometry information
        if master.winfo_ismapped():
            m_width = master.winfo_width()
            m_height = master.winfo_height()
            m_x = master.winfo_rootx()
            m_y = master.winfo_rooty()
        else:
            m_width = master.winfo_screenwidth()
            m_height = master.winfo_screenheight()
            m_x = m_y = 0
        w_width = widget.winfo_reqwidth()
        w_height = widget.winfo_reqheight()
        x = m_x + (m_width - w_width) * relx
        y = m_y + (m_height - w_height) * rely
        if x+w_width > master.winfo_screenwidth():
            x = master.winfo_screenwidth() - w_width
        elif x < 0:
            x = 0
        if y+w_height > master.winfo_screenheight():
            y = master.winfo_screenheight() - w_height
        elif y < 0:
            y = 0
        widget.geometry("+%d+%d" % (x, y))
        widget.deiconify() # Become visible at the desired location

    def go(self):
        self.root.wait_visibility()
        self.root.grab_set()
        self.root.mainloop()
        self.root.destroy()
        return self.num

    def return_event(self, event):
        if self.default is None:
            self.root.bell()
        else:
            self.done(self.default)

    def wm_delete_window(self, event=None):
        if self.cancel is None:
            self.root.bell()
        else:
            self.done(self.cancel)

    def done(self, num):
        self.num = num
        self.root.quit()


# TODO
# 通过xpath获取element内部数据和内容
def get_xpath_elements(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        txt = setting.get('fr_html_content')
        tx2 = setting.get('fr_local_set')
        tx4 = setting.get('fr_parse_info')

        c_set = tx2.get(0.,tkinter.END).strip()
        xp = '//html'
        toggle = 0
        for i in c_set.splitlines():
            i = i.strip()
            if i.startswith('<') and i.endswith('>'):
                if i.startswith('<xpath:'):
                    xp = re.findall('<xpath:(.*)>', i)[0].strip()
                    toggle = 1
                    break
                if i.startswith('<auto_list_xpath:'):
                    q = ['//div', '//li', '//tr']
                    d = {}
                    p = None
                    for j in tx4.get(0.,tkinter.END).strip().splitlines():
                        v = re.findall(r'^\[ xpath \]: (.*)$',j)
                        if v:
                            p = v[0]
                            d[p] = 0
                        elif p:
                            d[p] += 1
                    show_num = 25
                    m = sorted(d,key=lambda i:-d[i])[:show_num]
                    for i in m: q.append('[ cnt:{} ]'.format(d[i]) + i)
                    d = SimpleDialog(nb,
                        text=("是否选择自动解析出的xpath路径?\n"
                              "前三条内容为备选分析项\n"
                              "有时会有意想不到的效果\n"
                              "\n"
                              "(回车默认选择 “//div” ,最大展示{}条内容)".format(show_num)),
                        buttons=q,
                        default=0,
                        cancel=-1,
                        title="选则")
                    id = d.go()
                    if id == -1: return
                    xp = re.sub(r'^\[ cnt:\d+ \]', '', q[id])
                    toggle = 2
                    break
        tx4 = setting.get('fr_parse_info')
        if xp.startswith('//'):
            p = ['[ xpath ]: {}'.format(xp)]
            tree = etree.HTML(txt.get(0.,tkinter.END))
            idx = 0
            for x in tree.xpath(xp):
                xpth = get_simple_path_tail(x)
                strs = re.sub('\s+',' ',x.xpath('string(.)'))
                strs = strs[:40] + '...' if len(strs) > 40 else strs
                xpth = '[ xptail ]: {} {}'.format(xpth[1] if xpth else xpth, strs)
                p.append(xpth)
                idx += 1
            p.append('[ count ]: {}'.format(idx))
            content = '\n'.join(p)
        tx4.delete(0.,tkinter.END)
        tx4.insert(0.,content)
        if toggle == 0:
            tx2.delete(0.,tkinter.END)
            tx2.insert(0.,'<xpath://html>')
        if toggle == 2:
            tx2.delete(0.,tkinter.END)
            tx2.insert(0.,'<xpath:{}>'.format(xp))
        show_response_log()


# 通过xpath获取element内部数据和内容
def get_auto_xpath(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        txt = setting.get('fr_html_content')
        tx2 = setting.get('fr_local_set')

        c_set = tx2.get(0.,tkinter.END).strip()
        sx = ''
        toggle = True
        for i in c_set.splitlines():
            i = i.strip()
            if i.startswith('<') and i.endswith('>'):
                if i.startswith('<auto_list_xpath:'):
                    sx = re.findall('<auto_list_xpath:(.*)>', i)[0].strip()
                    toggle = False
        tx4 = setting.get('fr_parse_info')
        # 通过字符串自动分析列表文件路径的函数
        strs = re.split('\s+',sx)
        p = []
        idx = 0
        for xp,strs in get_xpath_by_str(strs,txt.get(0.,tkinter.END)):
            idx += 1
            q = []
            q.append('[ xpath ]: {}'.format(xp))
            for j,c in strs:
                q.append('    [ content ]: {} {}'.format(j,c))
            p.append(q)
        q = []
        for i in sorted(p,key=lambda i:len(i[0])):
            q.extend(i)
        q.append('[ count ]: {}'.format(idx))
        content = '\n'.join(q)
        tx4.delete(0.,tkinter.END)
        tx4.insert(0.,content)
        if toggle:
            tx2.delete(0.,tkinter.END)
            tx2.insert(0.,'<auto_list_xpath:>')
        show_response_log()



# 通过分析json结构内的数据生成对应的列表信息
def get_auto_json(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        txt = setting.get('fr_html_content')
        tx2 = setting.get('fr_local_set')

        c_set = tx2.get(0.,tkinter.END).strip()
        sx = ''
        toggle = True
        for i in c_set.splitlines():
            i = i.strip()
            if i.startswith('<') and i.endswith('>'):
                if i.startswith('<auto_list_json:'):
                    sx = re.findall('<auto_list_json:(.*)>', i)[0].strip()
                    toggle = False
        tx4 = setting.get('fr_parse_info')
        try:
            content = get_json_show(txt.get(0.,tkinter.END))
            if not content:
                try:
                    _content = 'cannot use auto parse json list\nnow use simple pprint json.\n===============================\n'
                    _content += pprint.pformat(parse_json_content(txt.get(0.,tkinter.END)))
                    content = _content
                except:
                    content = 'cannot format this content to json struct.\n'
                    content += '==========================================\n'
                    content += traceback.format_exc()
        except:
            content = traceback.format_exc()
        tx4.delete(0.,tkinter.END)
        tx4.insert(0.,content)
        if toggle:
            tx2.delete(0.,tkinter.END)
            tx2.insert(0.,'<auto_list_json:>')
        show_response_log()

# 选则对应的列表信息的数据
def choice_auto_json(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        # txt = setting.get('fr_html_content')
        tx2 = setting.get('fr_local_set')
        tx4 = setting.get('fr_parse_info')
        c_set = tx4.get(0., tkinter.END).split('\n\n')
        v = []
        vv = []
        for i in c_set:
            ls = []
            for j in i.splitlines():
                k = re.findall(r'^\[cnt:\d+\] jsondata[^\n]+', j)
                if k: v.append(k[0])
                ls.append(j)
            vv.append(ls)
        if not v: return
        d = SimpleDialog(nb,
            text="选则一条json列表进行解析,默认第一条内容。",
            buttons=v,
            default=0,
            cancel=-1,
            title="选则")
        id = d.go()
        if id == -1: return
        k = re.findall(r'^\[cnt:\d+\] (jsondata[^\n]+)', v[id])[0]
        content = '\n'.join(vv[id]).strip()
        tx4.delete(0.,tkinter.END)
        tx4.insert(0.,content)
        tx2.delete(0.,tkinter.END)
        tx2.insert(0.,'<auto_list_json:{}>'.format(k))

# 选则对应的列表信息的数据
def response_jsonformat(*a):
    _select = nb.select()
    setting = nb_names[_select]['setting']
    if setting.get('type') == 'response':
        txt = setting.get('fr_html_content')
        tx2 = setting.get('fr_local_set')
        content = txt.get(0., tkinter.END)
        jsondata = json.loads(content[content.find('{'):content.rfind('}')+1])
        txt.delete(0., tkinter.END)
        try:
            txt.insert(0., json.dumps(jsondata, ensure_ascii=False, indent=4))
        except:
            txt.insert(0., re.sub('[\uD800-\uDBFF][\uDC00-\uDFFF]|[\U00010000-\U0010ffff]','',json.dumps(jsondata, ensure_ascii=False, indent=4)))
        tx2.delete(0.,tkinter.END)
        tx2.insert(0.,'<just_json:>')




def execute_js_code():
    pass


def create_temp_idle():
    pythonexe = sys.executable
    pypath = os.path.split(pythonexe)[0]
    idlepyw = os.path.join(pypath,'Lib/idlelib/idle.pyw')
    tempfile = os.path.join(home, '.vrequest.py')
    if not os.path.isfile(tempfile):
        with open(tempfile, 'w', encoding='utf-8') as f:
            f.write('# -*- coding: utf-8 -*-')
    cmd = '{} {} {}'.format(pythonexe, idlepyw, tempfile)
    os.popen(cmd)


def create_cmd_idle():
    pythonexe = sys.executable
    pypath = os.path.split(pythonexe)[0]
    idlepyw = os.path.join(pypath,'Lib/idlelib/idle.pyw')
    rundefault = [
        'import os, sys, json, re, time, urllib',
        'import pprint, difflib, hmac, hashlib, html',
        'from urllib import request, parse',
        'from urllib.parse import quote,unquote,urlencode',
        'from urllib.request import urlopen'
    ]
    a = "print('default imports:')"
    b = "[print('    '+i,end='\\n') for i in {}] or None".format(rundefault)
    rundefault = ';'.join([a]+rundefault+[b])
    cmd = '{} {} -c "{}"'.format(pythonexe, idlepyw, rundefault)
    os.popen(cmd)


def creat_windows_shortcut():
    # 创建 windows 的桌面快捷方式。
    import os
    import sys
    import shutil
    import tempfile
    vbsscript = '''
set WshShell = WScript.CreateObject("WScript.Shell" )
set oShellLink = WshShell.CreateShortcut(Wscript.Arguments.Named("shortcut") & ".lnk")
oShellLink.TargetPath = Wscript.Arguments.Named("target")
oShellLink.IconLocation = Wscript.Arguments.Named("icon")
oShellLink.WindowStyle = 1
oShellLink.Save
    '''.strip()

    s = tempfile.mkdtemp()
    try:
        vbs = os.path.join(s, 'temp.vbs')
        with open(vbs, 'w', encoding='utf-8') as f:
            f.write(vbsscript)
        _exe  = os.path.join(os.path.split(sys.executable)[0], r'Scripts')
        _icon = os.path.join(os.path.split(sys.executable)[0], r'Lib\site-packages\vrequest')

        exe  = os.path.join(_exe, 'vv.exe')
        icon = os.path.join(_icon, 'ico.ico')
        link = os.path.join(os.path.expanduser("~"),'Desktop','vrequest')

        if os.path.isfile(link + '.lnk'):
            tkinter.messagebox.showinfo('Error','快捷方式已存在,如需重新生成,请删除现有的桌面快捷方式。')
            return

        cmd = r'''
        {} /target:{} /shortcut:"{}" /icon:{}
        '''.format(vbs, exe, link, icon).strip()
        print(cmd)
        v = os.popen(cmd)
        v.read()
        v.close()
    finally:
        import traceback;
        if traceback.format_exc().strip() != 'NoneType: None':
            print('create shortcut failed.')
            traceback.print_exc()
        shutil.rmtree(s)

def create_xpath_finder(setting):
    content = setting.get('content')
    master = setting.get('master')

    import tkinter
    from tkinter import ttk
    from tkinter import scrolledtext
    from tkinter.font import Font
    Text = scrolledtext.ScrolledText
    Frame = tkinter.Frame
    Entry = ttk.Entry
    Label = ttk.Label
    Listbox = tkinter.Listbox
    top = tkinter.Toplevel(master)
    ft = Font(family='Consolas',size=10)
    def normal_etree(e, tags=['script','style','select','noscript'], rootxpath='//html'):
        q = []
        for it in e.getiterator():
            if it.tag in tags or type(it.tag) is not str:
                q.append(it)
        for it in q:
            p = it.getparent()
            if p is not None:
                p.remove(it)
        return e
    e = etree.HTML(content)
    e = normal_etree(e)
    ls = []
    for i in e.xpath('//*'):
        xps = get_simple_path_tail(i) 
        if not xps: continue
        xp, sxp, key = xps
        v = e.xpath('string({})'.format(sxp))
        v = re.sub(r'\s+',' ',v).strip()
        if v and (sxp, v) not in ls:
            ls.append((sxp, v))

    fr0 = Frame(top)
    fr0.pack(fill=tkinter.BOTH,expand=True)
    def change_func(*content):
        if content:
            lbx.delete(0, tkinter.END)
            mxl = 0
            for sxp, v in ls: mxl = len(sxp) if len(sxp) > mxl else mxl
            fmt = '{:<3}[len:{:>4}:{:>2}] {:<' + str(mxl + 3) + '}'
            lens = 0
            for idx, (sxp, v) in enumerate(ls, 1):
                if content[0] in v:
                    lbx.insert("end", fmt.format(idx, len(v), mxl, sxp) + (v[:20]+'...'+v[-20:] if len(v) > 40 else v))
                    lens += 1
            lb1['text'] = '过滤 当前解析数量 [{}]'.format(lens)
            return True
    ech = top.register(change_func)
    fr1 = Frame(fr0)
    lb1 = Label(fr1, text='过滤 当前解析数量 [{}]'.format(len(ls)))
    en1 = Entry(fr1,validate='key',validatecommand=(ech, '%P'))
    en1.bind('<Key>', ech)
    fr1.pack(fill=tkinter.BOTH)
    lb1.pack(fill=tkinter.BOTH)
    en1.pack(fill=tkinter.BOTH)

    def double_click(*a):
        content = lbx.get(lbx.curselection())
        mxl = int(re.findall(r'\[len: *\d+: *(\d+)\]', content)[0])
        content = content[17:18+mxl].strip()
        for sxp, v in ls:
            if sxp == content:
                tx1.delete(0., tkinter.END)
                tx1.insert(tkinter.END, v)
                tx2.insert(tkinter.END, content+'\n')
    fr2 = Frame(fr0)
    lbx = Listbox(fr2,width=180,height=30,font=ft)
    fr2.pack(fill=tkinter.BOTH,expand=True)
    lbx.pack(fill=tkinter.BOTH,expand=True)
    lbx.bind('<Double-Button-1>', double_click)

    fr3 = Frame(fr0)
    tx1 = Text(fr3)
    tx2 = Text(fr3)
    fr3.pack(fill=tkinter.BOTH,expand=True)
    tx1.pack(fill=tkinter.BOTH,expand=True,side=tkinter.LEFT)
    tx2.pack(fill=tkinter.BOTH,expand=True,side=tkinter.LEFT)
    mxl = 0
    for sxp, v in ls: mxl = len(sxp) if len(sxp) > mxl else mxl
    fmt = '{:<3}[len:{:>4}:{:>2}] {:<' + str(mxl + 3) + '}'
    ls = sorted(ls, key=lambda i:len(i[1]))[::-1]
    for idx, (sxp, v) in enumerate(ls, 1):
        lbx.insert("end", fmt.format(idx, len(v), mxl, sxp) + (v[:20]+'...'+v[-20:] if len(v) > 40 else v))
    ret = ''
    def return_event(event):
        nonlocal ret 
        ret = tx2.get(0.,tkinter.END).strip()
        top.quit()
    def wm_delete_window(event=None): 
        nonlocal ret
        ret = tx2.get(0.,tkinter.END).strip()
        top.quit()
    top.title('双击选则需要的xpath,关闭该窗口时若xpath列表窗口中有则传入配置,后续生成代码中会增加相关代码。')
    top.bind('<Return>', return_event)
    top.bind('<Escape>', wm_delete_window)
    top.protocol('WM_DELETE_WINDOW', wm_delete_window)
    top.wait_visibility()
    top.grab_set()
    top.mainloop()
    top.destroy()
    return ret

def pipinstall_all(*a):
    import os, sys
    pip3_exe = os.path.join(os.path.split(sys.executable)[0], r'Scripts', r'pip3.exe')
    libs = 'scrapy js2py jsbeautifier cryptography pillow pyzbar'
    try:
        cmd = 'start powershell -NoExit "{}" install {}'.format(pip3_exe, libs)
        os.system(cmd)
    except:
        cmd = 'start cmd /k "{}" install {}'.format(pip3_exe, libs)
        os.system(cmd)