[
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.gitignore.io/api/macos\n\n### macOS ###\n*.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# End of https://www.gitignore.io/api/macos\n\n# debug file\n_site"
  },
  {
    "path": ".travis.yml",
    "content": "language: ruby\nenv:\n  global:\n    - NOKOGIRI_USE_SYSTEM_LIBRARIES=true\ninstall: \n  - gem install jekyll\n  - gem install jekyll-paginate\nscript: \n  - jekyll build\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n  \n\n"
  },
  {
    "path": "404.html",
    "content": "---\nlayout: default\ndescription: \"你来到了没有知识的荒原 🙊\"\nheader-img: \"img/404-bg.jpg\"\npermalink: /404.html\n---\n\n\n<!-- Page Header -->\n<header class=\"intro-header\" style=\"background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')\">\n\t<div class=\"container\">\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n\t\t\t\t<div class=\"site-heading\" id=\"tag-heading\">\n\t\t\t\t\t<h1>404</h1>\n\t\t\t\t\t<span class=\"subheading\">{{ page.description }}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</header>\n\n<script>\n\tdocument.body.classList.add('page-fullscreen');\n</script>\n"
  },
  {
    "path": "5ebc4602a40a61502b09471e378ae7ae.txt",
    "content": "2c7be435d8efb8289f7b6c9517e6391ff0cb857c"
  },
  {
    "path": "CNAME",
    "content": "powerbipro.cn"
  },
  {
    "path": "Gruntfile.js",
    "content": "module.exports = function(grunt) {\n\n    // Project configuration.\n    grunt.initConfig({\n        pkg: grunt.file.readJSON('package.json'),\n        uglify: {\n            main: {\n                src: 'js/<%= pkg.name %>.js',\n                dest: 'js/<%= pkg.name %>.min.js'\n            }\n        },\n        less: {\n            expanded: {\n                options: {\n                    paths: [\"css\"]\n                },\n                files: {\n                    \"css/<%= pkg.name %>.css\": \"less/<%= pkg.name %>.less\"\n                }\n            },\n            minified: {\n                options: {\n                    paths: [\"css\"],\n                    cleancss: true\n                },\n                files: {\n                    \"css/<%= pkg.name %>.min.css\": \"less/<%= pkg.name %>.less\"\n                }\n            }\n        },\n        banner: '/*!\\n' +\n            ' * <%= pkg.title %> v<%= pkg.version %> (<%= pkg.homepage %>)\\n' +\n            ' * Copyright <%= grunt.template.today(\"yyyy\") %> <%= pkg.author %>\\n' +\n            ' */\\n',\n        usebanner: {\n            dist: {\n                options: {\n                    position: 'top',\n                    banner: '<%= banner %>'\n                },\n                files: {\n                    src: ['css/<%= pkg.name %>.css', 'css/<%= pkg.name %>.min.css', 'js/<%= pkg.name %>.min.js']\n                }\n            }\n        },\n        watch: {\n            scripts: {\n                files: ['js/<%= pkg.name %>.js'],\n                tasks: ['uglify'],\n                options: {\n                    spawn: false,\n                },\n            },\n            less: {\n                files: ['less/*.less'],\n                tasks: ['less'],\n                options: {\n                    spawn: false,\n                }\n            },\n        },\n    });\n\n    // Load the plugins.\n    grunt.loadNpmTasks('grunt-contrib-uglify');\n    grunt.loadNpmTasks('grunt-contrib-less');\n    grunt.loadNpmTasks('grunt-banner');\n    grunt.loadNpmTasks('grunt-contrib-watch');\n\n    // Default task(s).\n    grunt.registerTask('default', ['uglify', 'less', 'usebanner']);\n\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 BY \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "![image-20220408103920034](https://raw.githubusercontent.com/xueqiandata/picgo/main/image-20220408103920034.png)\n\n[![Build Status](https://travis-ci.org/qiubaiying/qiubaiying.github.io.svg?branch=master)](https://travis-ci.org/qiubaiying/qiubaiying.github.io)\n[![codebeat badge](https://codebeat.co/badges/5f031df3-f6c1-4ec0-911a-ff6617ca50b9)](https://codebeat.co/projects/github-com-qiubaiying-qiubaiying-github-io-master)\n[![GitHub issues](https://img.shields.io/github/issues/qiubaiying/qiubaiying.github.io.svg?style=flat)](https://github.com/qiubaiying/qiubaiying.github.io/issues)\n[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/home-assistant/home-assistant-iOS/blob/master/LICENSE)\n[![](https://img.shields.io/github/stars/qiubaiying/qiubaiying.github.io.svg?style=social&label=Star)](https://github.com/qiubaiying/qiubaiying.github.io)\n[![](https://img.shields.io/github/forks/qiubaiying/qiubaiying.github.io.svg?style=social&label=Fork)](https://github.com/qiubaiying/qiubaiying.github.io)\n\n\n博客的搭建教程修改自 [Hux](https://github.com/Huxpro/huxpro.github.io) \n\n更为详细的教程戳这 [《利用 GitHub Pages 快速搭建个人博客》](http://www.jianshu.com/p/e68fba58f75c) 或 [wiki](https://github.com/qiubaiying/qiubaiying.github.io/wiki/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B)\n\n>\n### [查看博客戳这里 👆](http://qiubaiying.github.io)\n\n\n\n## 使用\n\n* 开始\n\t* [环境](#环境)\n\t* [开始](#开始)\n\t* [撰写博文](#撰写博文)\n* 组件\n\t* [侧边栏](#侧边栏)\n\t* [迷你关于我](#mini-about-me)\n\t* [推荐标签](#featured-tags)\n\t* [好友链接](#friends)\n\t* [HTML5 演示文档布局](#keynote-layout)\n* 评论与 Google/Baidu Analytics\n\t* [评论](#comment)\n\t* [网站分析](#analytics) \n* 高级部分\n\t* [自定义](#customization)\n\t* [标题底图](#header-image)\n\t* [搜索展示标题-头文件](#seo-title)\n\n\n\n### 环境\n\n如果你安装了 [jekyll](http://jekyllcn.com/)，那你只需要在命令行输入`jekyll serve` 或 `jekyll s`就能在本地浏览器中输入`http://127.0.0.1:4000/`预览主题，对主题的修改也能实时展示（需要强刷浏览器）。\n\n\n\n### 开始\n\n你可以通用修改 `_config.yml`文件来轻松的开始搭建自己的博客:\n\n```\n# Site settings\ntitle: BY Blog                    # 你的博客网站标题\nSEOTitle: 柏荧的博客 | BY Blog\t\t# SEO 标题\ndescription: \"Hey\"\t   \t   # 随便说点，描述一下\n\n# SNS settings      \ngithub_username: qiubaiying     # 你的github账号\njianshu_username: e71990ada2fd  # 你的简书ID。\n\n# Build settings\n# paginate: 10              # 一页你准备放几篇文章\n```\n\nJekyll官方网站还有很多的参数可以调，比如设置文章的链接形式...网址在这里：[Jekyll - Official Site](http://jekyllrb.com/) 中文版的在这里：[Jekyll中文](http://jekyllcn.com/).\n\n### 撰写博文\n\n要发表的文章一般以 **Markdown** 的格式放在这里`_posts/`，你只要看看这篇模板里的文章你就立刻明白该如何设置。\n\nyaml 头文件长这样:\n\n```\n---\nlayout:     post\ntitle:      定时器 你真的会使用吗？\nsubtitle:   iOS定时器详解\ndate:       2016-12-13\nauthor:     BY\nheader-img: img/post-bg-ios9-web.jpg\ncatalog: \t true\ntags:\n    - iOS\n    - 定时器\n---\n\n```\n\n### 侧边栏\n\n看右边:\n![](https://raw.githubusercontent.com/qiubaiying/qiubaiying.github.io/master/img/readme-side.png)\n\n设置是在 `_config.yml`文件里面的`Sidebar settings`那块。\n\n```\n# Sidebar settings\nsidebar: true  #添加侧边栏\nsidebar-about-description: \"简单的描述一下你自己\"\nsidebar-avatar: /img/avatar-by.jpg     #你的大头贴，请使用绝对地址.注意：名字区分大小写！后缀名也是\n```\n\n侧边栏是响应式布局的，当屏幕尺寸小于992px的时候，侧边栏就会移动到底部。具体请见bootstrap栅格系统 <http://v3.bootcss.com/css/>\n\n\n### Mini About Me\n\nMini-About-Me 这个模块将在你的头像下面，展示你所有的社交账号。这个也是响应式布局，当屏幕变小时候，会将其移动到页面底部，只不过会稍微有点小变化，具体请看代码。\n\n### Featured Tags\n\n看到这个网站 [Medium](http://medium.com) 的推荐标签非常的炫酷，所以我将他加了进来。\n这个模块现在是独立的，可以呈现在所有页面，包括主页和发表的每一篇文章标题的头上。\n\n```\n# Featured Tags\nfeatured-tags: true  \nfeatured-condition-size: 1     # A tag will be featured if the size of it is more than this condition value\n```\n\n唯一需要注意的是`featured-condition-size`: 如果一个标签的 SIZE，也就是使用该标签的文章数大于上面设定的条件值，这个标签就会在首页上被推荐。\n\n内部有一个条件模板 `{% if tag[1].size > {{site.featured-condition-size}} %}` 是用来做筛选过滤的.\n\n### Social-media Account\n\n在下面输入的社交账号，没有的添加的不会显示在侧边框中。新加入了[简书](https:/www.jianshu.com)链接, <http://www.jianshu.com/u/e71990ada2fd>\n\n\t# SNS settings\n\tRSS: false\n\tjianshu_username: \tjianshu_id \n\tzhihu_username:     username\n\tfacebook_username:  username\n\tgithub_username:    username\n\t# weibo_username:   username\n\n\n​\t\n\n![](http://ww4.sinaimg.cn/large/006tKfTcgy1fgrgbgf77aj308i02v748.jpg)\n\n### Friends\n\n好友链接部分。这会在全部页面显示。\n\n设置是在 `_config.yml`文件里面的`Friends`那块，自己加吧。\n\n```\n# Friends\nfriends: [\n    {\n        title: \"BY Blog\",\n        href: \"https://qiubaiying.github.io/\"\n    },\n    {\n        title: \"Apple\",\n        href: \"https://apple.com/\"\n    }\n]\n```\n\n\n### Keynote Layout\n\nHTML5幻灯片的排版：\n\n![](https://camo.githubusercontent.com/f30347a118171820b46befdf77e7b7c53a5641a9/687474703a2f2f6875616e677875616e2e6d652f696d672f626c6f672d6b65796e6f74652e6a7067)\n\n这部分是用于占用html格式的幻灯片的，一般用到的是 Reveal.js, Impress.js, Slides, Prezi 等等.我认为一个现代化的博客怎么能少了放html幻灯的功能呢~\n\n其主要原理是添加一个 `iframe`，在里面加入外部链接。你可以直接写到头文件里面去，详情请见下面的yaml头文件的写法。\n\n```\n---\nlayout:     keynote\niframe:     \"http://huangxuan.me/js-module-7day/\"\n---\n```\n\niframe在不同的设备中，将会自动的调整大小。保留内边距是为了让手机用户可以向下滑动，以及添加更多的内容。\n\n\n### Comment\n\n博客不仅支持 [Disqus](http://disqus.com) 评论系统,还加入了 [Gitalk](https://gitalk.github.io/) 评论系统，[支持 Markdwon 语法](https://guides.github.com/features/mastering-markdown/)，cool~\n\n#### Disqus\n\n优点：国际比较流行，界面也很大气、简洁，如果有人评论，还能实时通知，直接回复通知的邮件就行了；\n\n缺点：评论必须要去注册一个disqus账号，分享一般只有Facebook和Twitter，另外在墙内加载速度略慢了一点。想要知道长啥样，可以看以前的版本点[这里](http://brucezhaor.github.io/about.html) 最下面就可以看到。\n\n> Node：有很多人反映 Disqus 插件加载不出来，可能墙又架高了，有条件的话翻个墙就好了~\n\n**使用：**\n\n**首先**，你需要去注册一个Disqus帐号。**不要直接使用我的啊！**\n\n**其次**，你只需要在下面的 yaml 头文件中设置一下就可以了。\n\n```\n# 评论系统\n# Disqus（https://disqus.com/）\ndisqus_username: qiubaiying\n```\n\n#### Gitalk\n\n优点：界面干净简洁，利用 Github issue API 做的评论插件，使用 Github 帐号进行登录和评论，最喜欢的支持 Markdown 语法，对于程序员来说真是太 cool 了。\n\n缺点：配置比较繁琐，每篇文章的评论都需要初始化。\n\n**使用：**\n\n参考我的这篇文章：[《为博客添加 Gitalk 评论插件》](http://qiubaiying.top/2017/12/19/%E4%B8%BA%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0-Gitalk-%E8%AF%84%E8%AE%BA%E6%8F%92%E4%BB%B6/)\n\n\n### Analytics\n\n网站分析，现在支持百度统计和Google Analytics。需要去官方网站注册一下，然后将返回的code贴在下面：\n\n```\n# Baidu Analytics\nba_track_id: 4cc1f2d8f3067386cc5cdb626a202900\n\n# Google Analytics\nga_track_id: 'UA-49627206-1'            # 你用Google账号去注册一个就会给你一个这样的id\nga_domain: huangxuan.me\t\t\t# 默认的是 auto, 这里我是自定义了的域名，你如果没有自己的域名，需要改成auto。\n```\n\n### Customization\n\n如果你喜欢折腾，你可以去自定义这个模板的 Code。\n\n**如果你可以理解 `_include/` 和 `_layouts/`文件夹下的代码（这里是整个界面布局的地方），你就可以使用 Jekyll 使用的模版引擎 [Liquid](https://github.com/Shopify/liquid/wiki)的语法直接修改/添加代码，来进行更有创意的自定义界面啦！**\n\n### Header Image\n\n博客每页的标题底图是可以自己选的，看看几篇示例post你就知道如何设置了。\n\n标题底图的选取完全是看个人的审美了。每一篇文章可以有不同的底图，你想放什么就放什么，最后宽度要够，大小不要太大，否则加载慢啊。\n\n> 上传的图片最好先压缩，这里推荐 imageOptim 图片压缩软件，让你的博客起飞。\n\n但是需要注意的是本模板的标题是**白色**的，所以背景色要设置为**灰色**或者**黑色**，总之深色系就对了。当然你还可以自定义修改字体颜色，总之，用github pages就是可以完全的个性定制自己的博客。\n\n### SEO Title\n\n我的博客标题是 **“BY Blog”** 但是我想要在搜索的时候显示 **“柏荧的博客 | BY Blog”** ，这个就需要 SEO Title 来定义了。\n\n其实这个 SEO Title 就是定义了<head><title>标题</title></head>这个里面的东西和多说分享的标题，你可以自行修改的。\n\n### 关于收到\"Page Build Warning\"的 Email\n\n由于jekyll升级到3.0.x,对原来的 pygments 代码高亮不再支持，现只支持一种-rouge，所以你需要在 `_config.yml`文件中修改`highlighter: rouge`.另外还需要在`_config.yml`文件中加上`gems: [jekyll-paginate]`.\n\n同时,你需要更新你的本地 jekyll 环境.\n\n使用`jekyll server`的同学需要这样：\n\n1. `gem update jekyll` # 更新jekyll\n2. `gem update github-pages` #更新依赖的包\n\n使用`bundle exec jekyll server`的同学在更新 jekyll 后，需要输入`bundle update`来更新依赖的包.\n\n> Note：\n> 可以使用 `jekyll -s` 命令在本地实时配置博客，提高效率。详见 [Jekyll.com](http://jekyllcn.com/)\n\n参考文档：[using jekyll with pages](https://help.github.com/articles/using-jekyll-with-pages/) & [Upgrading from 2.x to 3.x](http://jekyllrb.com/docs/upgrading/2-to-3/)\n\n\n## 致谢\n\n1. 这个模板是从这里 [Hux](https://github.com/Huxpro/huxpro.github.io) fork 的, 感谢这个作者。 \n2. 感谢 Jekyll、Github Pages 和 Bootstrap!\n\n## License\n\n遵循 MIT 许可证。有关详细,请参阅 [LICENSE](https://github.com/qiubaiying/qiubaiying.github.io/blob/master/LICENSE)。\n\n"
  },
  {
    "path": "_config.yml",
    "content": "# Site settings\ntitle: 学谦PowerBI\nSEOTitle: 学谦PowerBI\nheader-img: img/post-bg-desk.jpg\nemail: xueqian@powerbipro.cn\ndescription: \"Every failure is leading towards success.\"\nkeyword: \"学谦, 学谦 Blog, 学谦的博客, xueqian, 陈学谦, iOS, Apple, iPhone\"\nurl: \"http://powerbipro.cn/\"          # your host, for absolute URL\nbaseurl: \"\"      # for example, '/blog' if your blog hosted on 'host/blog'\ngithub_repo: \"https://github.com/xueqiandata/xueqiandata.github.io.git\" # you code repository\n\n# Sidebar settings\nsidebar: true                           # whether or not using Sidebar.\nsidebar-about-description: \"满招损，谦受益！\"\nsidebar-avatar: /img/about-学谦.png      # use absolute URL, seeing it's used in both `/` and `/about/`\n\n\n\n# SNS settings\nRSS: false\n# weibo_username:     xueqiandata\nzhihu_username:     xueqiandata\ngithub_username:    xueqiandata\n#twitter_username:   xueqiandata\n\n\n\n\n# Build settings\n# from 2016, 'pygments' is unsupported on GitHub Pages. Use 'rouge' for highlighting instead.\npermalink: pretty\npaginate: 10\nexclude: [\"less\",\"node_modules\",\"Gruntfile.js\",\"package.json\",\"README.md\"]\nanchorjs: true                          # if you want to customize anchor. check out line:181 of `post.html`\n\n\n\n# Gems\n# from PR#40, to support local preview for Jekyll 3.0\ngems: [jekyll-paginate]\n\n\n\n\n# Markdown settings\n# replace redcarpet to kramdown,\n# although redcarpet can auto highlight code, the lack of header-id make the catalog impossible, so I switch to kramdown\n# document: http://jekyllrb.com/docs/configuration/#kramdown\nmarkdown: kramdown\nhighlighter: rouge\nkramdown:\n  input: GFM                            # use Github Flavored Markdown !important\n\n\n\n# 评论系统\n# Disqus（https://disqus.com/）\n# disqus_username: xueqiandata\n\n# Gitalk\ngitalk:\n  enable: true    #是否开启Gitalk评论\n  clientID: 14a053a5e3f367d2e515                            #生成的clientID\n  clientSecret: 350401a27d8a01c2c5a657242449692fd592d42a    #生成的clientSecret\n  repo: xueqiandata.github.io    #仓库名称\n  owner: xueqiandata    #github用户名\n  admin: xueqiandata\n  distractionFreeMode: true #是否启用类似FB的阴影遮罩\n\n\n# 统计\n\n# Analytics settings\n# Baidu Analytics\nba_track_id: b50bf2b12b5338a1845e33832976fd68\n\n# Google Analytics\nga_track_id: 'UA-90855596-1'            # Format: UA-xxxxxx-xx\nga_domain: qiubaiying.top               # 默认的是 auto, 这里我是自定义了的域名，你如果没有自己的域名，需要改成auto\n\n\n\n\n\n# Featured Tags\nfeatured-tags: true                     # 是否使用首页标签\nfeatured-condition-size: 1              # 相同标签数量大于这个数，才会出现在首页\n\n\n\n# Progressive Web Apps\nchrome-tab-theme-color: \"#000000\"\nservice-worker: true\n\n\n\n# Friends\nfriends: [\n    {\n        title: \"公众号·学谦\",\n        href: \"https://mp.weixin.qq.com/s/OrDqdlriIMkNZ_GzYUS92w\"\n    },{\n        title: \"知乎·学谦\",\n        href: \"https://www.zhihu.com/people/xueqiandata\"\n    },{\n        title: \"Apple\",\n        href: \"https://apple.com\"\n    },{\n        title: \"Apple Developer\",\n        href: \"https://developer.apple.com/\"\n    }\n]\n"
  },
  {
    "path": "_includes/footer.html",
    "content": "<!-- Footer -->\n<footer>\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                <ul class=\"list-inline text-center\">\n                    {% if site.RSS %}\n                    <li>\n                        <a href=\"{{ \"/feed.xml\" | prepend: site.baseurl }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa fa-rss fa-stack-1x fa-inverse\"></i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    <!-- add jianshu add target = \"_blank\" to <a> by BY -->\n                    {% if site.jianshu_username %}\n                            <li>\n                                <a target=\"_blank\" href=\"https://www.jianshu.com/u/{{ site.jianshu_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa  fa-stack-1x fa-inverse\">简</i>\n                                    </span>\n                                </a>\n                            </li>\n                    {% endif %}\n                    {% if site.twitter_username %}\n                    <li>\n                        <a href=\"https://twitter.com/{{ site.twitter_username }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa fa-twitter fa-stack-1x fa-inverse\"></i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n\n                    <!-- add Weibo, Zhihu by Hux, add target = \"_blank\" to <a> by Hux -->\n                    {% if site.zhihu_username %}\n                    <li>\n                        <a target=\"_blank\" href=\"https://www.zhihu.com/people/{{ site.zhihu_username }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa  fa-stack-1x fa-inverse\">知</i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if site.weibo_username %}\n                    <li>\n                        <a target=\"_blank\" href=\"http://weibo.com/{{ site.weibo_username }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa fa-weibo fa-stack-1x fa-inverse\"></i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n\n\n                    {% if site.facebook_username %}\n                    <li>\n                        <a target=\"_blank\" href=\"https://www.facebook.com/{{ site.facebook_username }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa fa-facebook fa-stack-1x fa-inverse\"></i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if site.github_username %}\n                    <li>\n                        <a target=\"_blank\" href=\"https://github.com/{{ site.github_username }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa fa-github fa-stack-1x fa-inverse\"></i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if site.linkedin_username %}\n                    <li>\n                        <a target=\"_blank\" href=\"https://www.linkedin.com/in/{{ site.linkedin_username }}\">\n                            <span class=\"fa-stack fa-lg\">\n                                <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                <i class=\"fa fa-linkedin fa-stack-1x fa-inverse\"></i>\n                            </span>\n                        </a>\n                    </li>\n                    {% endif %}\n                </ul>\n                <p class=\"copyright text-muted\">\n                    Copyright &copy; {{ site.title }} {{ site.time | date: '%Y' }}\n                    <br>\n                    Theme on <a href=\"{{ site.github_repo }}\">GitHub</a> |\n                    <iframe\n                        style=\"margin-left: 2px; margin-bottom:-5px;\"\n                        frameborder=\"0\" scrolling=\"0\" width=\"100px\" height=\"20px\"\n                        src=\"https://ghbtns.com/github-btn.html?user={{ site.github_username }}&repo={{ site.github_username }}.github.io&type=star&count=true\" >\n                    </iframe>\n                </p>\n            </div>\n        </div>\n    </div>\n</footer>\n\n<!-- jQuery -->\n<script src=\"{{ \"/js/jquery.min.js \" | prepend: site.baseurl }}\"></script>\n\n<!-- Bootstrap Core JavaScript -->\n<script src=\"{{ \"/js/bootstrap.min.js \" | prepend: site.baseurl }}\"></script>\n\n<!-- Custom Theme JavaScript -->\n<script src=\"{{ \"/js/hux-blog.min.js \" | prepend: site.baseurl }}\"></script>\n\n<!-- Service Worker -->\n{% if site.service-worker %}\n<script type=\"text/javascript\">\n    if(navigator.serviceWorker){\n        // For security reasons, a service worker can only control the pages that are in the same directory level or below it. That's why we put sw.js at ROOT level.\n        navigator.serviceWorker\n            .register('/sw.js')\n            .then((registration) => {console.log('Service Worker Registered. ', registration)})\n            .catch((error) => {console.log('ServiceWorker registration failed: ', error)})\n    }\n</script>\n{% endif %}\n\n\n<!-- async load function -->\n<script>\n    function async(u, c) {\n      var d = document, t = 'script',\n          o = d.createElement(t),\n          s = d.getElementsByTagName(t)[0];\n      o.src = u;\n      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }\n      s.parentNode.insertBefore(o, s);\n    }\n</script>\n\n<!-- \n     Because of the native support for backtick-style fenced code blocks \n     right within the Markdown is landed in Github Pages, \n     From V1.6, There is no need for Highlight.js, \n     so Huxblog drops it officially.\n\n     - https://github.com/blog/2100-github-pages-now-faster-and-simpler-with-jekyll-3-0  \n     - https://help.github.com/articles/creating-and-highlighting-code-blocks/ \n     - https://github.com/jneen/rouge/wiki/list-of-supported-languages-and-lexers   \n-->\n<!--\n    <script>\n        async(\"http://cdn.bootcss.com/highlight.js/8.6/highlight.min.js\", function(){\n            hljs.initHighlightingOnLoad();\n        })\n    </script>\n    <link href=\"http://cdn.bootcss.com/highlight.js/8.6/styles/github.min.css\" rel=\"stylesheet\">\n-->\n\n\n<!-- jquery.tagcloud.js -->\n<script>\n    // only load tagcloud.js in tag.html\n    if($('#tag_cloud').length !== 0){\n        async('{{ \"/js/jquery.tagcloud.js\" | prepend: site.baseurl }}',function(){\n            $.fn.tagcloud.defaults = {\n                //size: {start: 1, end: 1, unit: 'em'},\n                color: {start: '#bbbbee', end: '#0085a1'},\n            };\n            $('#tag_cloud a').tagcloud();\n        })\n    }\n</script>\n\n<!--fastClick.js -->\n<script>\n    async(\"//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js\", function(){\n        var $nav = document.querySelector(\"nav\");\n        if($nav) FastClick.attach($nav);\n    })\n</script>\n\n\n<!-- Google Analytics -->\n{% if site.ga_track_id %}\n<script>\n    // dynamic User by Hux\n    var _gaId = '{{ site.ga_track_id }}';\n    var _gaDomain = '{{ site.ga_domain }}';\n\n    // Originial\n    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n    ga('create', _gaId, _gaDomain);\n    ga('send', 'pageview');\n</script>\n{% endif %}\n\n\n<!-- Baidu Tongji -->\n{% if site.ba_track_id %}\n<script>\n    // dynamic User by Hux\n    var _baId = '{{ site.ba_track_id }}';\n\n    // Originial\n    var _hmt = _hmt || [];\n    (function() {\n      var hm = document.createElement(\"script\");\n      hm.src = \"//hm.baidu.com/hm.js?\" + _baId;\n      var s = document.getElementsByTagName(\"script\")[0];\n      s.parentNode.insertBefore(hm, s);\n    })();\n</script>\n{% endif %}\n\n\n\n<!-- Side Catalog -->\n{% if page.catalog %}\n<script type=\"text/javascript\">\n    function generateCatalog (selector) {\n        var P = $('div.post-container'),a,n,t,l,i,c;\n        a = P.find('h1,h2,h3,h4,h5,h6');\n        a.each(function () {\n            n = $(this).prop('tagName').toLowerCase();\n            i = \"#\"+$(this).prop('id');\n            t = $(this).text();\n            c = $('<a href=\"'+i+'\" rel=\"nofollow\">'+t+'</a>');\n            l = $('<li class=\"'+n+'_nav\"></li>').append(c);\n            $(selector).append(l);\n        });\n        return true;    \n    }\n\n    generateCatalog(\".catalog-body\");\n\n    // toggle side catalog\n    $(\".catalog-toggle\").click((function(e){\n        e.preventDefault();\n        $('.side-catalog').toggleClass(\"fold\")\n    }))\n\n    /*\n     * Doc: https://github.com/davist11/jQuery-One-Page-Nav\n     * Fork by Hux to support padding\n     */\n    async(\"{{ '/js/jquery.nav.js' | prepend: site.baseurl }}\", function () {\n        $('.catalog-body').onePageNav({\n            currentClass: \"active\",\n            changeHash: !1,\n            easing: \"swing\",\n            filter: \"\",\n            scrollSpeed: 700,\n            scrollOffset: 0,\n            scrollThreshold: .2,\n            begin: null,\n            end: null,\n            scrollChange: null,\n            padding: 80\n        });\n    });\n</script>\n{% endif %}\n\n"
  },
  {
    "path": "_includes/head.html",
    "content": "<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"google-site-verification\" content=\"xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"{{ site.description }}\">\n    <meta name=\"keywords\"  content=\"{{ site.keyword }}\">\n    <meta name=\"theme-color\" content=\"{{ site.chrome-tab-theme-color }}\">\n    \n    <title>{% if page.title %}{{ page.title }} - {{ site.SEOTitle }}{% else %}{{ site.SEOTitle }}{% endif %}</title>\n\n    <!-- Web App Manifest -->\n    <link rel=\"manifest\" href=\"{{ site.baseurl }}/pwa/manifest.json\">\n\n    <!-- Favicon -->\n    <link rel=\"shortcut icon\" href=\"{{ site.baseurl }}/img/favicon.ico\">\n\n    <!-- Safari Webpage Icon    by-BY -->\n    <link rel=\"apple-touch-icon\" href=\"{{ site.baseurl }}/img/apple-touch-icon.png\">\n    \n    <!-- Canonical URL -->\n    <link rel=\"canonical\" href=\"{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}\">\n\n    <!-- Bootstrap Core CSS -->\n    <link rel=\"stylesheet\" href=\"{{ \"/css/bootstrap.min.css\" | prepend: site.baseurl }}\">\n\n    <!-- Custom CSS -->\n    <link rel=\"stylesheet\" href=\"{{ \"/css/hux-blog.min.css\" | prepend: site.baseurl }}\">\n\n    <!-- Pygments Github CSS -->\n    <link rel=\"stylesheet\" href=\"{{ \"/css/syntax.css\" | prepend: site.baseurl }}\">\n\n    <!-- Custom Fonts -->\n    <!-- <link href=\"http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css\" rel=\"stylesheet\" type=\"text/css\"> -->\n    <!-- Hux change font-awesome CDN to qiniu -->\n    <link href=\"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css\" rel=\"stylesheet\" type=\"text/css\">\n\n\n    <!-- Hux Delete, sad but pending in China\n    <link href='http://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>\n    <link href='http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/\n    css'>\n    -->\n\n\n    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->\n    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->\n    <!--[if lt IE 9]>\n        <script src=\"https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js\"></script>\n        <script src=\"https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js\"></script>\n    <![endif]-->\n\n    <!-- ga & ba script hoook -->\n    <script></script>\n</head>\n"
  },
  {
    "path": "_includes/nav.html",
    "content": "<!-- Navigation -->\n<nav class=\"navbar navbar-default navbar-custom navbar-fixed-top\">\n    <div class=\"container-fluid\">\n        <!-- Brand and toggle get grouped for better mobile display -->\n        <div class=\"navbar-header page-scroll\">\n            <button type=\"button\" class=\"navbar-toggle\">\n                <span class=\"sr-only\">Toggle navigation</span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n            </button>\n            <a class=\"navbar-brand\" href=\"{{ site.baseurl }}/\">{{ site.title }}</a>\n        </div>\n\n        <!-- Collect the nav links, forms, and other content for toggling -->\n        <div id=\"huxblog_navbar\">\n            <div class=\"navbar-collapse\">\n                <ul class=\"nav navbar-nav navbar-right\">\n                    <li>\n                        <a href=\"{{ site.baseurl }}/\">Home</a>\n                    </li>\n                    {% for page in site.pages %}{% if page.title %}\n                    <li>\n                        <a href=\"{{ page.url | prepend: site.baseurl }}\">{{ page.title }}</a>\n                    </li>\n                    {% endif %}{% endfor %}\n                </ul>\n            </div>\n        </div>\n        <!-- /.navbar-collapse -->\n    </div>\n    <!-- /.container -->\n</nav>\n<script>\n    // Drop Bootstarp low-performance Navbar\n    // Use customize navbar with high-quality material design animation\n    // in high-perf jank-free CSS3 implementation\n    var $body   = document.body;\n    var $toggle = document.querySelector('.navbar-toggle');\n    var $navbar = document.querySelector('#huxblog_navbar');\n    var $collapse = document.querySelector('.navbar-collapse');\n\n    var __HuxNav__ = {\n        close: function(){\n            $navbar.className = \" \";\n            // wait until animation end.\n            setTimeout(function(){\n                // prevent frequently toggle\n                if($navbar.className.indexOf('in') < 0) {\n                    $collapse.style.height = \"0px\"\n                }\n            },400)\n        },\n        open: function(){\n            $collapse.style.height = \"auto\"\n            $navbar.className += \" in\";\n        }\n    }\n\n    // Bind Event\n    $toggle.addEventListener('click', function(e){\n        if ($navbar.className.indexOf('in') > 0) {\n            __HuxNav__.close()\n        }else{\n            __HuxNav__.open()\n        }\n    })\n\n    /**\n     * Since Fastclick is used to delegate 'touchstart' globally\n     * to hack 300ms delay in iOS by performing a fake 'click',\n     * Using 'e.stopPropagation' to stop 'touchstart' event from \n     * $toggle/$collapse will break global delegation.\n     * \n     * Instead, we use a 'e.target' filter to prevent handler\n     * added to document close HuxNav.  \n     *\n     * Also, we use 'click' instead of 'touchstart' as compromise\n     */\n    document.addEventListener('click', function(e){\n        if(e.target == $toggle) return;\n        if(e.target.className == 'icon-bar') return;\n        __HuxNav__.close();\n    })\n</script>\n"
  },
  {
    "path": "_layouts/default.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n{% include head.html %}\n\n<!-- hack iOS CSS :active style -->\n<body ontouchstart=\"\">\n\n    {% include nav.html %}\n\n    {{ content }}\n\n    {% include footer.html %}\n\n\n<!-- Image to hack wechat -->\n<img src=\"/img/apple-touch-icon.png\" width=\"0\" height=\"0\" />\n<!-- Migrate from head to bottom, no longer block render and still work -->\n\n</body>\n\n</html>\n"
  },
  {
    "path": "_layouts/keynote.html",
    "content": "---\nlayout: default\n---\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\"> -->\n<!-- <img src=\"{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}\" width=\"0\" height=\"0\"> -->\n\n<!-- Post Header -->\n<style type=\"text/css\">\n    header.intro-header{\n        /*background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')*/\n        height: 500px;\n        overflow: hidden;\n    }\n    header iframe{\n        width: 100%;\n        height: 100%;\n        border: 0;\n    }\n    /* Override Nav Style */\n    {% if page.navcolor == \"invert\" %}\n        .navbar-custom .nav li a,\n        .navbar-custom .nav li a:hover,\n        .navbar-custom .navbar-brand,\n        .navbar-custom .navbar-brand:hover {color:#777;}\n        .navbar-default .navbar-toggle .icon-bar {background-color:#777;}\n\n    {% endif %}\n</style>\n<header class=\"intro-header\" >\n    <iframe src=\"{{page.iframe}}\"/>\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                    <div class=\"post-heading\">\n                        <div class=\"tags\">\n                            {% for tag in page.tags %}\n                            <a class=\"tag\" href=\"{{ site.baseurl }}/tags/#{{ tag }}\" title=\"{{ tag }}\">{{ tag }}</a>\n                            {% endfor %}\n                        </div>\n                        <h1>{{ page.title }}</h1>\n                        {% comment %}\n                            always create a h2 for keeping the margin , Hux\n                        {% endcomment %}\n                        {% comment %} if page.subtitle {% endcomment %}\n                        <h2 class=\"subheading\">{{ page.subtitle }}</h2>\n                        {% comment %} endif {% endcomment %}\n                        <span class=\"meta\">Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %} on {{ page.date | date: \"%B %-d, %Y\" }}</span>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </iframe>\n</header>\n\n<!-- Post Content -->\n<article>\n    <div class=\"container\">\n        <div class=\"row\">\n\n            <!-- Post Container -->\n            <div class=\"post-container\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1 \">\n\n\t\t\t\t{{ content }}\n\n                <hr style=\"visibility: hidden;\">\n\n\n                <ul class=\"pager\">\n                    {% if page.previous.url %}\n                    <li class=\"previous\">\n                        <a href=\"{{ page.previous.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.previous.title}}\">\n                        Previous<br>\n                        <span>{{page.previous.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if page.next.url %}\n                    <li class=\"next\">\n                        <a href=\"{{ page.next.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.next.title}}\">\n                        Next<br>\n                        <span>{{page.next.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                </ul>\n\n                <!-- Gitalk 评论 start  -->\n                {% if site.gitalk.enable %}\n                <!-- Gitalk link  -->\n                <link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\">\n                <script src=\"https://unpkg.com/gitalk@latest/dist/gitalk.min.js\"></script>\n\n                <div id=\"gitalk-container\"></div>\n                    <script type=\"text/javascript\">\n                    var gitalk = new Gitalk({\n                    clientID: '{{site.gitalk.clientID}}',\n                    clientSecret: '{{site.gitalk.clientSecret}}',\n                    repo: '{{site.gitalk.repo}}',\n                    owner: '{{site.gitalk.owner}}',\n                    admin: ['{{site.gitalk.admin}}'],\n                    id: window.location.pathname,\n                    });\n                    gitalk.render('gitalk-container');\n                </script>\n                {% endif %}\n                <!-- Gitalk end -->\n\n                {% if site.disqus.enable %}\n                <!-- disqus 评论框 start -->\n                <div class=\"comment\">\n                    <div id=\"disqus_thread\" class=\"disqus-thread\">\n\n                    </div>\n                </div>\n                <!-- disqus 评论框 end -->\n                {% endif %}\n\n            </div>\n\n            <!-- Sidebar Container -->\n            <div class=\"sidebar-container\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1 \">\n\n                <!-- Featured Tags -->\n                {% if site.featured-tags %}\n                <section>\n                    <hr class=\"hidden-sm hidden-xs\">\n                    <h5><a href=\"/tags/\">FEATURED TAGS</a></h5>\n                    <div class=\"tags\">\n                        {% for tag in site.tags %}\n                            {% if tag[1].size > {{site.featured-condition-size}} %}\n                                <a href=\"/tags/#{{ tag[0] }}\" title=\"{{ tag[0] }}\" rel=\"{{ tag[1].size }}\">\n                                    {{ tag[0] }}\n                                </a>\n                            {% endif %}\n                        {% endfor %}\n                    </div>\n                </section>\n                {% endif %}\n\n                <!-- Friends Blog -->\n                {% if site.friends %}\n                <hr>\n                <h5>FRIENDS</h5>\n                <ul class=\"list-inline\">\n                    {% for friend in site.friends %}\n                        <li><a href=\"{{friend.href}}\">{{friend.title}}</a></li>\n                    {% endfor %}\n                </ul>\n                {% endif %}\n            </div>\n        </div>\n    </div>\n</article>\n\n<!-- resize header to fullscreen keynotes -->\n<script>\n    var $header = document.getElementsByTagName(\"header\")[0];\n    function resize(){\n        /*\n         * leave 85px to both\n         * - told/imply users that there has more content below\n         * - let user can scroll in mobile device, seeing the keynote-view is unscrollable\n         */\n        $header.style.height = (window.innerHeight-85) + 'px';\n    }\n    document.addEventListener('DOMContentLoaded', function(){\n        resize();\n    })\n    window.addEventListener('load', function(){\n        resize();\n    })\n    window.addEventListener('resize', function(){\n        resize();\n    })\n    resize();\n</script>\n\n\n\n{% if site.disqus.enable %}\n<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->\n<script type=\"text/javascript\">\n    /* * * CONFIGURATION VARIABLES * * */\n    var disqus_shortname = \"{{site.disqus.username}}\";\n    var disqus_identifier = \"{{page.id}}\";\n    var disqus_url = \"{{site.url}}{{page.url}}\";\n\n    (function() {\n        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';\n        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n    })();\n</script>\n<!-- disqus 公共JS代码 end -->\n{% endif %}\n\n\n{% if site.anchorjs %}\n<!-- async load function -->\n<script>\n    function async(u, c) {\n      var d = document, t = 'script',\n          o = d.createElement(t),\n          s = d.getElementsByTagName(t)[0];\n      o.src = u;\n      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }\n      s.parentNode.insertBefore(o, s);\n    }\n</script>\n<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->\n<script>\n    async(\"//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js\",function(){\n        anchors.options = {\n          visible: 'always',\n          placement: 'right',\n          icon: '#'\n        };\n        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');\n    })\n</script>\n<style>\n    /* place left on bigger screen */\n    @media all and (min-width: 800px) {\n        .anchorjs-link{\n            position: absolute;\n            left: -0.75em;\n            font-size: 1.1em;\n            margin-top : -0.1em;\n        }\n    }\n</style>\n{% endif %}\n"
  },
  {
    "path": "_layouts/page.html",
    "content": "---\nlayout: default\n---\n\n<!-- Page Header -->\n<header class=\"intro-header\" style=\"background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')\">\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 \">\n                <div class=\"site-heading\">\n                    <h1>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</h1>\n                    <!--<hr class=\"small\">-->\n                    <span class=\"subheading\">{{ page.description }}</span>\n                </div>\n            </div>\n        </div>\n    </div>\n</header>\n\n<!-- Main Content -->\n<div class=\"container\">\n\t<div class=\"row\">\n        {% if site.sidebar == false %}\n<!-- NO SIDEBAR -->\n    <!-- PostList Container -->\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 postlist-container\">\n                {{ content }}\n            </div>\n    <!-- Sidebar Container -->\n            <div class=\"\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1\n                sidebar-container\">\n\n                <!-- Featured Tags -->\n                {% if site.featured-tags %}\n                <section>\n                    <!-- no hr -->\n                    <h5><a href=\"{{'/tags/' | prepend: site.baseurl }}\">FEATURED TAGS</a></h5>\n                    <div class=\"tags\">\n        \t\t\t\t{% for tag in site.tags %}\n                            {% if tag[1].size > {{site.featured-condition-size}} %}\n                \t\t\t\t<a href=\"{{ site.baseurl }}/tags/#{{ tag[0] }}\" title=\"{{ tag[0] }}\" rel=\"{{ tag[1].size }}\">\n                                    {{ tag[0] }}\n                                </a>\n                            {% endif %}\n        \t\t\t\t{% endfor %}\n        \t\t\t</div>\n                </section>\n                {% endif %}\n\n                <!-- Friends Blog -->\n                {% if site.friends %}\n                <hr>\n                <h5>FRIENDS</h5>\n                <ul class=\"list-inline\">\n                    {% for friend in site.friends %}\n                        <li><a href=\"{{friend.href}}\">{{friend.title}}</a></li>\n                    {% endfor %}\n                </ul>\n                {% endif %}\n            </div>\n        {% else %}\n\n<!-- USE SIDEBAR -->\n    <!-- PostList Container -->\n    \t\t<div class=\"\n                col-lg-8 col-lg-offset-1\n                col-md-8 col-md-offset-1\n                col-sm-12\n                col-xs-12\n                postlist-container\n            \">\n    \t\t\t{{ content }}\n    \t\t</div>\n    <!-- Sidebar Container -->\n            <div class=\"\n                col-lg-3 col-lg-offset-0\n                col-md-3 col-md-offset-0\n                col-sm-12\n                col-xs-12\n                sidebar-container\n            \">\n                <!-- Featured Tags -->\n                {% if site.featured-tags %}\n                <section>\n                    <hr class=\"hidden-sm hidden-xs\">\n                    <h5><a href=\"{{'/tags/' | prepend: site.baseurl }}\">FEATURED TAGS</a></h5>\n                    <div class=\"tags\">\n                        {% for tag in site.tags %}\n                            {% if tag[1].size > {{site.featured-condition-size}} %}\n                                <a href=\"{{ site.baseurl }}/tags/#{{ tag[0] }}\" title=\"{{ tag[0] }}\" rel=\"{{ tag[1].size }}\">\n                                    {{ tag[0] }}\n                                </a>\n                            {% endif %}\n                        {% endfor %}\n                    </div>\n                </section>\n                {% endif %}\n\n                <!-- Short About -->\n                <section class=\"visible-md visible-lg\">\n                    <hr><h5><a href=\"{{'/about/' | prepend: site.baseurl }}\">ABOUT ME</a></h5>\n                    <div class=\"short-about\">\n                        {% if site.sidebar-avatar %}\n                            <a href=\"{{ site.baseurl }}/about\">\n                                <img src=\"{{site.sidebar-avatar}}\"/>\n                            </a>\n                        {% endif %}\n                        {% if site.sidebar-about-description %}\n                            <p>{{site.sidebar-about-description}}</p>\n                        {% endif %}\n                        <!-- SNS Link -->\n                        <ul class=\"list-inline\">\n                            {% if site.RSS %}\n                            <li>\n                                <a href=\"{{ \"/feed.xml\" | prepend: site.baseurl }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa fa-rss fa-stack-1x fa-inverse\"></i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            <!-- add jianshu add target = \"_blank\" to <a> by BY -->\n                            {% if site.jianshu_username %}\n                            <li>\n                                <a target=\"_blank\" href=\"https://www.jianshu.com/u/{{ site.jianshu_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa  fa-stack-1x fa-inverse\">简</i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            {% if site.twitter_username %}\n                            <li>\n                                <a href=\"https://twitter.com/{{ site.twitter_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa fa-twitter fa-stack-1x fa-inverse\"></i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            {% if site.zhihu_username %}\n                            <li>\n                                <a target=\"_blank\" href=\"https://www.zhihu.com/people/{{ site.zhihu_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa  fa-stack-1x fa-inverse\">知</i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            {% if site.weibo_username %}\n                            <li>\n                                <a target=\"_blank\" href=\"http://weibo.com/{{ site.weibo_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa fa-weibo fa-stack-1x fa-inverse\"></i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            {% if site.facebook_username %}\n                            <li>\n                                <a target=\"_blank\" href=\"https://www.facebook.com/{{ site.facebook_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa fa-facebook fa-stack-1x fa-inverse\"></i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            {% if site.github_username %}\n                            <li>\n                                <a target=\"_blank\" href=\"https://github.com/{{ site.github_username }}\">\n                                    <span class=\"fa-stack fa-lg\">\n                                        <i class=\"fa fa-circle fa-stack-2x\"></i>\n                                        <i class=\"fa fa-github fa-stack-1x fa-inverse\"></i>\n                                    </span>\n                                </a>\n                            </li>\n                            {% endif %}\n                            \n                        </ul>\n                        {% if site.email %}\n                            <p>✉️ {{site.email}}</p>\n                        {% endif %}\n                    </div>\n                </section>\n                <!-- Friends Blog -->\n                {% if site.friends %}\n                <hr>\n                <h5>FRIENDS</h5>\n                <ul class=\"list-inline\">\n                    {% for friend in site.friends %}\n                        <li><a href=\"{{friend.href}}\">{{friend.title}}</a></li>\n                    {% endfor %}\n                </ul>\n                {% endif %}\n    \t\t</div>\n        {% endif %}\n\t</div>\n</div>\n"
  },
  {
    "path": "_layouts/post.html",
    "content": "---\nlayout: default\n---\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\"> -->\n<!-- <img src=\"{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}\" width=\"0\" height=\"0\"> -->\n\n<!-- Post Header -->\n<style type=\"text/css\">\n    header.intro-header{\n        position: relative;\n        background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')\n    }\n\n    {% if page.header-mask %}\n    header.intro-header .header-mask{\n        width: 100%;\n        height: 100%;\n        position: absolute;\n        background: rgba(0,0,0, {{ page.header-mask }});\n    }\n    {% endif %}\n</style>\n<header class=\"intro-header\" >\n    <div class=\"header-mask\"></div>\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                <div class=\"post-heading\">\n                    <div class=\"tags\">\n                        {% for tag in page.tags %}\n                        <a class=\"tag\" href=\"{{ site.baseurl }}/tags/#{{ tag }}\" title=\"{{ tag }}\">{{ tag }}</a>\n                        {% endfor %}\n                    </div>\n                    <h1>{{ page.title }}</h1>\n                    {% comment %}\n                        always create a h2 for keeping the margin , Hux\n                    {% endcomment %}\n                    {% comment %} if page.subtitle {% endcomment %}\n                    <h2 class=\"subheading\">{{ page.subtitle }}</h2>\n                    {% comment %} endif {% endcomment %}\n                    <span class=\"meta\">Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %} on {{ page.date | date: \"%B %-d, %Y\" }}</span>\n                </div>\n            </div>\n        </div>\n    </div>\n</header>\n\n<!-- Post Content -->\n<article>\n    <div class=\"container\">\n        <div class=\"row\">\n\n    <!-- Post Container -->\n            <div class=\"\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1\n                post-container\">\n\n\t\t\t\t{{ content }}\n\n                <hr style=\"visibility: hidden;\">\n\n                <ul class=\"pager\">\n                    {% if page.previous.url %}\n                    <li class=\"previous\">\n                        <a href=\"{{ page.previous.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.previous.title}}\">\n                        Previous<br>\n                        <span>{{page.previous.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if page.next.url %}\n                    <li class=\"next\">\n                        <a href=\"{{ page.next.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.next.title}}\">\n                        Next<br>\n                        <span>{{page.next.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                </ul>\n\n\n                <!--Gitalk评论start  -->\n                {% if site.gitalk.enable %}\n                <!-- 引入Gitalk评论插件  -->\n                <link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\">\n                <script src=\"https://unpkg.com/gitalk@latest/dist/gitalk.min.js\"></script>\n                <div id=\"gitalk-container\"></div>\n                <!-- 引入一个生产md5的js，用于对id值进行处理，防止其过长 -->\n                <!-- Thank DF:https://github.com/NSDingFan/NSDingFan.github.io/issues/3#issuecomment-407496538 -->\n                <script src=\"{{ site.baseurl }}/js/md5.min.js\"></script>\n                <script type=\"text/javascript\">\n                    var gitalk = new Gitalk({\n                    clientID: '{{site.gitalk.clientID}}',\n                    clientSecret: '{{site.gitalk.clientSecret}}',\n                    repo: '{{site.gitalk.repo}}',\n                    owner: '{{site.gitalk.owner}}',\n                    admin: ['{{site.gitalk.admin}}'],\n                    distractionFreeMode: {{site.gitalk.distractionFreeMode}},\n                    id: md5(location.pathname),\n                    });\n                    gitalk.render('gitalk-container');\n                </script>\n                {% endif %}\n                <!-- Gitalk end -->\n\n                {% if site.disqus_username %}\n                <!-- disqus 评论框 start -->\n                <div class=\"comment\">\n                    <div id=\"disqus_thread\" class=\"disqus-thread\"></div>\n                </div>\n                <!-- disqus 评论框 end -->\n                {% endif %}\n\n            </div>  \n\n    <!-- Side Catalog Container -->\n        {% if page.catalog %}\n            <div class=\"\n                col-lg-2 col-lg-offset-0\n                visible-lg-block\n                sidebar-container\n                catalog-container\">\n                <div class=\"side-catalog\">\n                    <hr class=\"hidden-sm hidden-xs\">\n                    <h5>\n                        <a class=\"catalog-toggle\" href=\"#\">CATALOG</a>\n                    </h5>\n                    <ul class=\"catalog-body\"></ul>\n                </div>\n            </div>\n        {% endif %}\n\n    <!-- Sidebar Container -->\n            <div class=\"\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1\n                sidebar-container\">\n\n                <!-- Featured Tags -->\n                {% if site.featured-tags %}\n                <section>\n                    <hr class=\"hidden-sm hidden-xs\">\n                    <h5><a href=\"/tags/\">FEATURED TAGS</a></h5>\n                    <div class=\"tags\">\n        \t\t\t\t{% for tag in site.tags %}\n                            {% if tag[1].size > {{site.featured-condition-size}} %}\n                \t\t\t\t<a href=\"/tags/#{{ tag[0] }}\" title=\"{{ tag[0] }}\" rel=\"{{ tag[1].size }}\">\n                                    {{ tag[0] }}\n                                </a>\n                            {% endif %}\n        \t\t\t\t{% endfor %}\n        \t\t\t</div>\n                </section>\n                {% endif %}\n\n                <!-- Friends Blog -->\n                {% if site.friends %}\n                <hr>\n                <h5>FRIENDS</h5>\n                <ul class=\"list-inline\">\n                    {% for friend in site.friends %}\n                        <li><a href=\"{{friend.href}}\">{{friend.title}}</a></li>\n                    {% endfor %}\n                </ul>\n                {% endif %}\n            </div>\n        </div>\n    </div>\n</article>\n\n\n{% if site.disqus_username %}\n<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->\n<script type=\"text/javascript\">\n    /* * * CONFIGURATION VARIABLES * * */\n    var disqus_shortname = \"{{site.disqus_username}}\";\n    var disqus_identifier = \"{{page.id}}\";\n    var disqus_url = \"{{site.url}}{{page.url}}\";\n\n    (function() {\n        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';\n        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n    })();\n</script>\n<!-- disqus 公共JS代码 end -->\n{% endif %}\n\n\n{% if site.anchorjs %}\n<!-- async load function -->\n<script>\n    function async(u, c) {\n      var d = document, t = 'script',\n          o = d.createElement(t),\n          s = d.getElementsByTagName(t)[0];\n      o.src = u;\n      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }\n      s.parentNode.insertBefore(o, s);\n    }\n</script>\n<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->\n<script>\n    async(\"//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js\",function(){\n        // BY Fix:去除标题前的‘#’ issues:<https://github.com/qiubaiying/qiubaiying.github.io/issues/137>\n        // anchors.options = {\n        //   visible: 'always',\n        //   placement: 'right',\n        //   icon: '#'\n        // };\n        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');\n    })\n</script>\n<style>\n    /* place left on bigger screen */\n    @media all and (min-width: 800px) {\n        .anchorjs-link{\n            position: absolute;\n            left: -0.75em;\n            font-size: 1.1em;\n            margin-top : -0.1em;\n        }\n    }\n</style>\n{% endif %}"
  },
  {
    "path": "_posts/2019-12-12-Power BI创建日期表的几种方式概览.md",
    "content": "Power BI创建日期表的几种方式概览\n\n几乎所有的报表模型都涉及到日期和时间，因此要创建Power BI报表，日期表就必须得有。虽然最新的Power BI版本已经可以自动为每一个时间列创建日期表。\n\n但这种方式还是存在明显缺点的，一方面如果日期列有两个及以上且分散在不同的table中，无法使用一对多关系来管理这些数据，更何况如果一个table中出现两个时间列（如订单日期和发货日期等）时就无法处理；另一方面，如果数据量特别大，或日期列比较多，自动创建的日期会严重影响性能，因此大部分情况下使用自动智能日期是不合适的。\n\n\n\n今天给大家介绍三个创建Power BI日期表的途径，分别对应着一种语言，Excel中的VBA语言，适用于Power BI和PowerPivot的DAX语言，适用于Power BI和PowerQuery的M语言，每一种途径都各有优势和劣势，大家可以视情况而定。\n\n\n\n第一种是VBA语言：\n\n直接用excel中的vba语言编写，通过添加简单的按钮可以实现一键创建日期表，并灵活修改起止日期。因为我这个项目的日期有特殊的要求，是截至到当前的，大家需要设置结束日期可以设置一个enddate来控制。\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOglaHGicq3pAN1LIkQ4eiaXvFm0eONC4RiblkSpH86ZyaF0Huux7GhNCkGJicNUUkTn2StibjO7MAbc69g/640?wx_fmt=png)\n\nSub date()\nDim i\nDim origin_date\nDim ws, w As Worksheet\n\nFor Each w In Worksheets\n  If w.Name <> \"使用说明\" Then\n    Application.DisplayAlerts = False\n    w.Delete\n    Application.DisplayAlerts = True\n  End If\nNext\n\norigin_date = Sheets(\"使用说明\").Range(\"G10\")\nSet ws = Worksheets.Add\nws.Name = \"日期\"\n\nws.Range(\"A:A\").NumberFormatLocal = \"YYYY-MM-DD\"\nws.Cells(1, 1) = \"日期\"\nws.Cells(2, 1) = origin_date\nFor i = 3 To DateDiff(\"d\", origin_date, Now) + 1\n  ws.Cells(i, 1) = ws.Cells(i - 1, 1) + 1\n\nNext i\n\nActiveWorkbook.SaveAs Path & \"\\date.xlsx\", FileFormat:=xlWorkbookDefault\n\nEnd Sub\n\n\n\n使用VBA来编写日期表的最大好处是完全不需要修改pbix文件，尤其是对于在线自动刷新的报表，将连接的日期表修改后，网关自动刷新，而无需重新发布报表。\n\n\n\n第二种是DAX语言：\n\n这是使用Power BI绕不过去的坎，需要人人掌握的。利用DAX生成日期表，使用几个不同的函数都可以做到，常用的有以下几种组合：\n\n1、ADDCOLUMNS与CALENDAR函数：\n日期表1 =\n\nADDCOLUMNS (\nCALENDAR (DATE(2017,1,1), DATE(2019,12,31)),\n\"年度\", YEAR ( [Date] ),\n\"季度\", \"Q\" & FORMAT ( [Date], \"Q\" ),\n\"月份\", FORMAT ( [Date], \"MM\" ),\n\"日\",FORMAT ( [Date], \"DD\" ),\n\"年度季度\", FORMAT ( [Date], \"YYYY\" ) & \"Q\" & FORMAT ( [Date], \"Q\" ),\n\"年度月份\", FORMAT ( [Date], \"YYYY/MM\" ),\n\"星期几\", WEEKDAY ( [Date],2 )\n)\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOglaHGicq3pAN1LIkQ4eiaXvFB7dSnU4sWHTbou0Qmzv6XIzhZ6ia6vTicfox9libibq8ZaH0C9y3EJl22Q/640?wx_fmt=png)\n\n2、GENERATE和CALENDAR函数\n日期表2=\n GENERATE (\n CALENDAR ( DATE ( 2017, 1, 1 ), DATE ( 2019, 12, 31 ) ),\n VAR currentDay = [Date]\n VAR year = YEAR ( currentDay )\n VAR quarter = \"Q\" & FORMAT ( currentDay, \"Q\" )\n VAR month = FORMAT ( currentDay, \"MM\" )\n VAR day = DAY( currentDay )\n VAR weekid = WEEKDAY ( currentDay,2)\n RETURN ROW (\n \"年度\", year ,\n \"季度\",quarter,\n \"月份\", month,\n \"日\", day,\n \"年度季度\", year&quarter,\n \"年度月份\", year&month,\n \"星期几\", weekid\n )\n )\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOglaHGicq3pAN1LIkQ4eiaXvFTPPEyHFXZddVtYwhPTDafhz3z9tdUdVaOXBzqjeMaoNvKBPISAodGw/640?wx_fmt=png)\n\n3、GENERATE与CALENDARAUTO函数\n日期表3=\n GENERATE (\n CALENDARAUTO(),\n VAR currentDay = [Date]\n VAR year = YEAR ( currentDay )\n VAR quarter = \"Q\" & FORMAT ( currentDay, \"Q\" )\n VAR month = FORMAT ( currentDay, \"MM\" )\n VAR day = DAY( currentDay )\n VAR weekid = WEEKDAY ( currentDay,2)\n RETURN ROW (\n \"年度\", year ,\n \"季度\",quarter,\n \"月份\", month,\n \"日\", day,\n \"年度季度\", year&quarter,\n \"年度月份\", year&month,\n \"星期几\", weekid\n )\n )\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOglaHGicq3pAN1LIkQ4eiaXvFaDr2GBhVriaQtcdFTPsCUC67NszRvYqUOvqeHoDS3JxFJciccSP9nHag/640?wx_fmt=png)\n\n这一段代码中并没有指定起止日期，这就是CALENDARAUTO函数的厉害之处，它可以自动检测模型中其他表中所有日期，然后生成涵盖这些日期的整年日期表。\n而且如果模型中其他表的日期范围发生变动，这个日期表也会自动更新到新的日期范围，利用CALENDARAUTO可以很轻松的制作一个动态的日期表。\n\n使用上面三种DAX函数生成日期表还有一个小小的遗憾，就是CALENDAR函数生成的日期列字段名都是英文的[Date]，而其他列都是中文，不过可以在生成日期表后进行手动更改，这个比较简单。第三种方法是使用M语言：对于很多Power BI使用者来说，尤其是没有接触过PowerQuery的人来说，M语言比较少用，也比较难一些，在这里直接给出表达式，复制粘贴即可。首先创建两个参数，kaishiDate和jieshuDate来确定起始日期和结束日期，然后在查询编辑器中，新建一个空查询，打开高级编辑器，粘贴以下代码，回车即可。let\n  日期序列= {Number.From(kaishiDate)..Number.From(jieshuDate)},\n  转换为表= Table.FromList(日期序列, Splitter.SplitByNothing(), null, null, ExtraValues.Error),\n  更改的类型= Table.TransformColumnTypes(转换为表,{{\"Column1\", type date}}),\n  重命名的列= Table.RenameColumns(更改的类型,{{\"Column1\", \"日期ID\"}}),\n  年= Table.AddColumn(重命名的列, \"年份序号\", each Date.Year([日期ID]),type number),\n  月= Table.AddColumn(年, \"月\", each Date.Month([日期ID]),type number),\n  月份名称= Table.AddColumn(月, \"月份名称\", each Date.ToText([日期ID],\"M月\"),type text),\n  年月序号= Table.AddColumn(月份名称, \"年月序号\", each Date.ToText([日期ID],\"yyyyMM\"),type number),\n  季度序号= Table.AddColumn(年月序号, \"季度序号\", each Date.QuarterOfYear([日期ID]),type number),\n  日= Table.AddColumn(季度序号, \"日\", each Date.Day([日期ID]),type number),\n  星期= Table.AddColumn(日, \"星期\", each Date.DayOfWeek([日期ID],0),type number)\nin\n  星期\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOglaHGicq3pAN1LIkQ4eiaXvF1AQlUmeDwMrWIfkaaeXqx7LTXicr1oLic9fHTX8QpHc9Jw1UCrMdIcNQ/640?wx_fmt=png)\n\n\n\n甚至更加霸道的完美版，参考佐罗老师的erBI战友联盟的文章，可以直接调用函数来创建，并且可以自定义设置。\n\n\n\nlet\n\n  CalendarType = type function (\n  \n    optional CalendarYearStart as (type number meta [\n      Documentation.FieldCaption = \"开始年份，日期表从开始年份1月1日起。\",\n      Documentation.FieldDescription = \"日期表从开始年份1月1日起\",\n      Documentation.SampleValues = { Date.Year( DateTime.LocalNow( ) ) - 1 } // Previous Year\n    ]),\n    \n    optional CalendarYearEnd as (type number meta [\n      Documentation.FieldCaption = \"结束年份，日期表至结束年份12月31日止。\",\n      Documentation.FieldDescription = \"日期表至结束年份12月31日止\",\n      Documentation.SampleValues = { Date.Year( DateTime.LocalNow( ) ) } // Current Year\n    ]),\n\n​    optional CalendarFirstDayOfWeek as (type text meta [\n​      Documentation.FieldCaption = \"定义一周开始日，从 Monday，Tuesday，Wednesday，Thursday，Friday，Saturday，Sunday中选择一个，缺省默认为Monday。\",\n​      Documentation.FieldDescription = \"从 Monday，Tuesday，Wednesday，Thursday，Friday，Saturday，Sunday中选择一个，缺省默认为Monday。\",\n​      Documentation.SampleValues = { \"Monday\" }\n​    ]),\n\n​    optional CalendarCulture as (type text meta [\n​      Documentation.FieldCaption = \"指定日期表显示月以及星期几的名称是中文或英文，en 表示英文，zh 表示中文，缺省默认与系统一致。\",\n​      Documentation.FieldDescription = \" en 表示英文，zh 表示中文，缺省默认与系统一致。\",\n​      Documentation.SampleValues = { \"zh\" }\n​    ])\n\n  )\n  as table meta [\n    Documentation.Name = \"构建日期表\",\n    Documentation.LongDescription = \"创建指定年份之间的日期表。并可进行各种设置。\",\n    Documentation.Examples = {\n    [\n      Description = \"返回当前年份日期表\",\n      Code = \"CreateCalendar()\",\n      Result = \"当前年份日期表。\"\n    ],\n    [\n      Description = \"返回指定年份的日期表\",\n      Code = \"CreateCalendar( 2017 )\",\n      Result = \"返回2017/01/01至2017/12/31之间的日期表。\"\n    ],\n    [\n      Description = \"返回起止年份之间的日期表\",\n      Code = \"CreateCalendar( 2015 , 2017 )\",\n      Result = \"返回2015/01/01至2017/12/31之间的日期表。\"\n    ],\n    [\n      Description = \"返回起止年份之间的日期表，并指定周二为每周的第一天\",\n      Code = \"CreateCalendar( 2015 , 2017 , \"\"Tuesday\"\" )\",\n      Result = \"2015/01/01至2017/12/31之间的日期表，且周二是每周的第一天。\"\n    ],\n    [\n      Description = \"返回起止年份之间的日期表，并指定周二为每周的第一天，并使用英文显示名称。\",\n      Code = \"CreateCalendar( 2015 , 2017 , \"\"Tuesday\"\", \"\"en\"\" )\",\n      Result = \"2015/01/01至2017/12/31之间的日期表，且周二是每周的第一天，并使用英文显示月名称及星期几的名称。\"\n    ]\n    }\n  ],\n  \n  \n  CreateCalendar = ( optional CalendarYearStart as number, optional CalendarYearEnd as number, optional CalendarFirstDayOfWeek as text, optional CalendarCulture as text) => let\n    begin_year = CalendarYearStart ,\n    end_year = CalendarYearEnd ,\n    first_day_of_week = if Text.Lower( CalendarFirstDayOfWeek ) = \"monday\" then Day.Monday\n              else if Text.Lower( CalendarFirstDayOfWeek ) = \"tuesday\" then Day.Tuesday\n              else if Text.Lower( CalendarFirstDayOfWeek ) = \"wednesday\" then Day.Wednesday\n              else if Text.Lower( CalendarFirstDayOfWeek ) = \"thursday\" then Day.Thursday\n              else if Text.Lower( CalendarFirstDayOfWeek ) = \"friday\" then Day.Friday\n              else if Text.Lower( CalendarFirstDayOfWeek ) = \"saturday\" then Day.Saturday\n              else if Text.Lower( CalendarFirstDayOfWeek ) = \"sunday\" then Day.Sunday\n              else if CalendarFirstDayOfWeek <> null then error \"参数错误：参数CalendarFirstDayOfWeek必须是Monday，Tuesday，Wednesday，Thursday，Friday，Saturday，Sunday中的一个。\"\n              else Day.Monday ,\n    culture = if CalendarCulture <> null then CalendarCulture else \"zh\" , // \"en\" , \"zh\"\n    y1 = if begin_year <> null then begin_year else if end_year <> null then end_year else Date.Year( DateTime.LocalNow() ) ,\n    y2 = if end_year <> null then end_year else if begin_year <> null then begin_year else Date.Year( DateTime.LocalNow() ) ,\n    calendar_list = { Number.From ( #date( Number.From( y1 ) , 1 , 1 ) ) .. Number.From( #date( Number.From( y2 ) , 12, 31 ) ) },\n    calendar_list_table = Table.FromList(calendar_list, Splitter.SplitByNothing(), null, null, ExtraValues.Error),\n    \\#\"Changed Type\" = Table.TransformColumnTypes(calendar_list_table,{{\"Column1\", type date}}),\n    \\#\"Renamed Columns\" = Table.RenameColumns(#\"Changed Type\",{{\"Column1\", \"Date\"}}),\n    \\#\"Inserted Year\" = Table.AddColumn(#\"Renamed Columns\", \"Year\", each Date.Year([Date]), Int64.Type),\n    \\#\"Inserted Quarter\" = Table.AddColumn(#\"Inserted Year\", \"Quarter\", each Date.QuarterOfYear([Date]), Int64.Type),\n    \\#\"Inserted Month\" = Table.AddColumn(#\"Inserted Quarter\", \"Month\", each Date.Month([Date]), Int64.Type),\n    \\#\"Inserted Week of Year\" = Table.AddColumn(#\"Inserted Month\", \"WeekOfYear\", each Date.WeekOfYear( [Date] , first_day_of_week ), Int64.Type),\n    \\#\"Inserted Week of Month\" = Table.AddColumn(#\"Inserted Week of Year\", \"WeekOfMonth\", each Date.WeekOfMonth( [Date] ), Int64.Type),\n    \\#\"Inserted Start of Week\" = Table.AddColumn(#\"Inserted Week of Month\", \"DateOfWeekStart\", each Date.StartOfWeek( [Date] ), type date),\n    \\#\"Inserted End of Week\" = Table.AddColumn(#\"Inserted Start of Week\", \"DateOfWeekEnd\", each Date.EndOfWeek([Date]), type date),\n    \\#\"Inserted Day\" = Table.AddColumn(#\"Inserted End of Week\", \"DayOfMonth\", each Date.Day([Date]), Int64.Type),\n    \\#\"Inserted Day of Week\" = Table.AddColumn(#\"Inserted Day\", \"DayOfWeek\", each Date.DayOfWeek( [Date] , first_day_of_week ), Int64.Type),\n    \\#\"Inserted Day of Year\" = Table.AddColumn(#\"Inserted Day of Week\", \"DayOfYear\", each Date.DayOfYear([Date]), Int64.Type),\n    \\#\"Inserted Day Name\" = Table.AddColumn(#\"Inserted Day of Year\", \"DayOfWeekName\", each Date.DayOfWeekName( [Date] , culture ), type text),\n    \\#\"Inserted Year Name\" = Table.AddColumn(#\"Inserted Day Name\", \"YearName\", each \"Y\" & Text.From( [Year] ) , type text ),\n    \\#\"Inserted Quarter Name\" = Table.AddColumn(#\"Inserted Year Name\", \"QuarterName\", each \"Q\" & Text.From( [Quarter] ) , type text ),\n    \\#\"Inserted Month Name\" = Table.AddColumn(#\"Inserted Quarter Name\", \"MonthName\", each Date.MonthName( [Date] , culture ), type text),\n    \\#\"Inserted Week Name\" = Table.AddColumn(#\"Inserted Month Name\", \"WeekName\", each \"W\" & Text.From( [WeekOfYear] ) , type text ),\n    \\#\"Inserted Year Quarter\" = Table.AddColumn(#\"Inserted Week Name\", \"YearQuarter\", each [Year] * 100 + [Quarter] , Int64.Type ),\n    \\#\"Inserted Year Month\" = Table.AddColumn(#\"Inserted Year Quarter\", \"YearMonth\", each [Year] * 100 + [Month] , Int64.Type ),\n    \\#\"Inserted Year Week\" = Table.AddColumn(#\"Inserted Year Month\", \"YearWeek\", each [Year] * 100 + [WeekOfYear] , Int64.Type ),\n    \\#\"Inserted Date Code\" = Table.AddColumn(#\"Inserted Year Week\", \"DateCode\", each [Year] * 10000 + [Month] * 100 + [DayOfMonth] , Int64.Type )\n  in\n    if culture = \"zh\"\n    then Table.RenameColumns( #\"Inserted Date Code\" ,\\{\\{\"Date\", \"日期\"}, {\"Year\", \"年\"}, {\"Quarter\", \"季\"}, {\"Month\", \"月\"}, {\"WeekOfYear\", \"周\"}, {\"WeekOfMonth\", \"月周\"}, {\"DayOfMonth\", \"月日\"}, {\"DateOfWeekStart\", \"周开始日期\"}, {\"DateOfWeekEnd\", \"周结束日期\"}, {\"DayOfWeek\", \"周天\"}, {\"DayOfYear\", \"年日\"}, {\"DayOfWeekName\", \"星期几名称\"}, {\"YearName\", \"年份名称\"}, {\"QuarterName\", \"季度名称\"}, {\"MonthName\", \"月份名称\"}, {\"WeekName\", \"周名称\"}, {\"YearQuarter\", \"年季\"}, {\"YearMonth\", \"年月\"}, {\"YearWeek\", \"年周\"}, {\"DateCode\", \"日期码\"}})\n    else #\"Inserted Date Code\"\n\nin\n  Value.ReplaceType( CreateCalendar , CalendarType )\n\n\n\n\n\nok，以上就是三个主要的创建日期表的途径，每一种都有自己的优缺点，具体来说\n1.VBA语言最大的好处是只需要修改原始文件，无需重新发布新的报表，缺点是需要用到另一门语言；\n\n2.DAX是最灵活的，也是日常都在用的，且用CALENDARAUTO函数可以自动识别模型中的最大最小日期，实现自动调整，缺点是需要修改相关标题；\n\n3.M语言是最强大的，通过参数自动化设置想要的各种各样的日期格式，缺点是如果日期表设置不合理，需要重新发布新的报表。\n\n\n\n以上三个途径都有多种表达式写法，追求简单的有简单的做法，追求完美的有完美的方式。当然，一般随着数据的越来越多，模型越来越复杂，对于日期表的需求也会不断地提升，可以适当采用添加列的方式创建更多符合业务需求的格式。\n"
  },
  {
    "path": "_posts/2020-01-15-如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？.md",
    "content": "如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78AS6MAPmxL8O1H3KYOXcwzBr1QdfMno9Sj9HZYQt2FsLnh7yKgvicxGg/640?wx_fmt=png)\n\n众所周知，powerbi的计划刷新支持每天更新8次，并且计划时间必须是整点或者半点两个选项，这对于很多需要及时刷新的数据来说太慢了，比如双十一、双十二的成交额数据，分毫必争，错失1分钟可能就会产生较严重的问题。\n\n更为严重的是，即便设定整点更新，按计划更新所需的时间执行至少需要10分钟。一开始我以为是数据量大的原因导致，结果，即便更换了一个报表，数据量特别小，只有一张表两行两列的数据，刷新时间仍然需要十几分钟，这我就接受不了了。（下图）\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOialqqR1b4UqDAIeuibOSVicgM6qKB3yZK9XVLRdJ4nFvLCLL4pOtEpQvkZNwotrnoUv7X4GLg1qBibuQ/640?wx_fmt=png)\n\n\n\n\n\n经过各种和世纪互联的沟(si)通(bi)，并请教了国外的专家，才得到一个明确的回复，这个事情就是这样，没办法，办不了，等着吧……\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOialqqR1b4UqDAIeuibOSVicgMzHiaHiaxOQ8Hkqd8MMQPhbPGHX07Ir0ibTZqcmmydibnibm91erJ8jqSscg/640?wx_fmt=png)\n\n\n\n我心想，这肯定不行啊……\n\n于是\n\npython大法用起来\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78SPZTq20epkZbdvFYXyiaygYwbsjrFa0OTkbtDeO9Z9iaYCKAsn7xgbeg/640?wx_fmt=jpeg)\n\n那么问题来了，怎么把大象装进冰箱里？\n\n分三步：\n\n第一步，买一个冰箱\n\n第二步，买一头大象\n\n第三步，把大象装进冰箱里。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78cDZ6BOwNvWgPWd9yJD34ibHA4KESiaSGUiacvEsmzpKkicjsyiaZL71QVHg/640?wx_fmt=jpeg)\n\n那么举一反三，我们就可以得出用Python大法自动刷新powerbi的步骤：\n\n第一步，安装python\n\n第二步，根据powerbi网页编写代码\n\n第三步，运行代码，葛优躺喝咖啡，美滋滋\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78AMoT4SOmNGgzwibEHtMYDvSyib7HEicU4D6EDlsx6PicyniavYZ5oD0qAicg/640?wx_fmt=jpeg)\n\n第一步不用说了，内事不决问度娘，外事不决问谷哥\n\n第三步也不用说了，编好了，狠狠地戳一下鼠标左键搞定\n\n\n\n说一下第二步：\n\n1.我们需要使用的库是selenium，一个第三方的Python库，可以模拟浏览器操作，是一个用于Web应用程序测试的工具。我们使用的selenium里的webdriver模块来操控浏览器。\n\nfrom selenium import webdriver\n\n2.接着，打开Firefox浏览器，路径是你的geckodriver.exe位置，这个在安装软件的时候可以设置的。\n\nbrower = webdriver.Firefox(executable_path=r'C:\\Program Files\\Mozilla Firefox\\geckodriver.exe')\n\n3.打开浏览器要输入网址，输入的是这个页面的网址，先拷贝下来，如下的格式：https://app.powerbi.cn/groups/xxxxxxxxxxxxxxxxxxxxxx/list/datasets\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78H4WZr3eJCOicBlHD8VE7UMHMBhmI2RiaaV7W5JHVcBm5fpev5icl0r9yQ/640?wx_fmt=png)\n\n将下面的链接改成你自己的链接：\n\nbrower.get(\"https://app.powerbi.cn/groups/xxxxxxxxxxxxx/list/datasets\")\n\n这样selenium就创建好了一个打开的网页，等待登陆\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G783WKGdaGgpF4ia0M6o7QPqIX0icayzeR03uKHTic6BiaqKEBYC9iaWw7I7uQ/640?wx_fmt=png)\n\n很明显，我们不能手动去填写账号和密码，太掉价了\n\n4.在填写用户名的地方右键-查看元素\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78beTPH0Iryc7N2BMIudMNrOe55GslONyic1Yl5Rtia1aEEbmx8kJWIiaRw/640?wx_fmt=png)\n\n发现有一个input id='i0116'，我们就通过这个id来确定输入框的位置，使用的是find_element_by_id，用send_keys输入账号，因为我使用的是国内的世纪互联的账号，你们改成自己的账号就行，国际版国内版代码是相同的。\n\nbrower.find_element_by_id('i0116').send_keys('xxxxx@xxxxxx.partner.onmschina.cn')\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78icoIW8p6f2TkkeoMWGhIFSx0XLDNhp3PCVgBtib5iaJA8xsugmBYiaxSZQ/640?wx_fmt=png)\n\n5.输入的账号后，我们应该点击下一步，在下一步的按钮处点击右键-查看元素，有一个id=\"idSIButton9\"，那么就好办了\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78aDucdzdRs9nYjlVGUibEYB4cvNGSRl22KEQd7soddgwhD4nlVOd8JJQ/640?wx_fmt=png)\n\nbrower.find_element_by_id('idSIButton9').click()\n\n这样就到了输入密码的界面\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G786EickTBKgFOS8FClv10wEVN9dWPjWZVprH0nA0c2sT37Nv4BqhEwrWg/640?wx_fmt=png)\n\n6.我们发现输入密码界面和输入账号界面是集备一致的，所以直接将代码写出来：\n\ntime.sleep(5)\n\nbrower.find_element_by_id('i0118').send_keys('duqkyg-qefby1-gipGun')\n\nbrower.find_element_by_id('idSIButton9').click()\n\n插入一个time.sleep(5)，表示暂停5秒，因为可能网速原因导致输入账号后的跳转需要一点点时间，这个可以自己调整。\n\n这样就直接进入到了数据集刷新的页面：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78uA8KvjGXbwVCSoI2hibiaJBeRxoiaxaicN5iaf1Kic4oz3e9Z8tAnIuLZS7g/640?wx_fmt=png)\n\n当然，在运行之后的代码前加入time.sleep(10)，因为powerbi网页对网速要求很高，看个人网速和电脑配置情况\n\n7.接下来就是要获取刷新按钮的位置并模拟点击了\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G782chSPyzqNaz2AtJJ2kE5dGQqz7HctltmibC2WW4Mv7CXZJOs4EZtI5Q/640?wx_fmt=png)\n\n\n\n仍然右键-查看元素\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78ibSxw8AFBxwibNMwxaYIBPzSiaQYTKhZJOdGmY5xZZnHdy4icXUGN6f4KA/640?wx_fmt=png)\n\n这里我们使用brower.find_element_by_xpath来确定元素的位置\n\nkeshi_refresh=brower.find_element_by_xpath(\".//*[@class='refreshNow pbi-glyph pbi-glyph-refresh' and @aria-describedby='主任课时提报管理datasetMenu2']\")\n\n确定完元素，就要模拟点击\n\nkeshi_refresh.click()\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78LUrLftER251BVRxgeCcp2YUP2cxyzwick9gg5icSt1Op2TYAdX10of3g/640?wx_fmt=png)\n\n上图最后一条就是刚刚按需刷新的记录。\n\n这样就完成了一次刷新。\n\n\n\n但是我们想要的是每隔10秒就进行一次刷新啊，而且是24小时不间断？！！\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78a6MFX7KkxRFn0Vvune1BWXOjStjibFDO9u0HUp8U0klJ6wYd0d721oA/640?wx_fmt=jpeg)\n\n\n\n只要创建一个死循环，10秒运行一下模拟点击click()就好：\n\nwhile True:\n\n​    keshi_refresh.click()\n\n​    time.sleep(10)\n\n\n\n效果如下：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78tqKLlakITkhqUaWy7AiaURWXGicM59I4fI09oxLm6T8hmXlrfdXK6wdA/640?wx_fmt=png)\n\n这样，我们就完成了利用Python来突破powerbi每天只有8次自动更新并且自动更新时间特别长的难题了。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78cevToftHmOoJPriamfBuGJiay4QQtngfdnpYYiajKPRaQaeWYxhchEYpg/640?wx_fmt=jpeg)\n\n那么问题就来了，如果雇一个人7d×24h不简单每10秒刷新一次Power BI，我需要每月支付他多少钱？\n\n\n\n完整源代码请关注【学谦数据运营】全网同名回复“1”获取。\n\n\n\n————————\n\n以上的使用selenium创建浏览器模拟点击刷新的方式已经可以做到完全不影响正常使用电脑的情况下进行。\n\n因为前几天有人在群里问，我随口说了一句，最简单的办法是找一台破电脑，用按键精灵10秒点击一次，需要占用一台电脑。如果有废旧电脑可以打开网页的话，是可以采用这种方式的。\n\n\n\n但是这种方式仍然有一个小小的问题，就是需要打开一个新的浏览器页面，并且如果按照10秒模拟点击一次，其实内存消耗还是比较大的，尤其是配置比较低的电脑。那么该怎么办呢？\n\n\n\n在点击刷新按钮的时候，右键网页-查看元素-网络，我们发现每一次刷新，其实就是代表着这一个post请求，那么只要我们将这个post请求的内容用Python发送出去，不就达到我们的目的了吗\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78yibiaHCXgic3uQv91z2PoJBEm7VqMYjhxcW9GldY8MYdiabiaoOZyJjJecw/640?wx_fmt=png)\n\n\n\n欲知后事如何，请关注【学谦数据运营】全网同名，等待下次更新。\n\n![image-20200410080923656](https://tva1.sinaimg.cn/large/00831rSTly1gdocop7cbbj31ao0hwhdt.jpg)"
  },
  {
    "path": "_posts/2020-01-16-一行代码无限刷新Power BI，完美突破每天8次限制.md",
    "content": "一行代码无限刷新Power BI，完美突破每天8次限制\n\n\n\n上次我们说到，使用selenium来操控浏览器打开网页，模拟点击进行刷新。\n\n但是这种方式仍然有一个小小的问题，就是需要打开一个新的浏览器页面，并且如果按照10秒模拟点击一次，其实内存消耗还是比较大的，尤其是配置比较低的电脑。\n\n好像遇到了一点小小的障碍……\n\n障碍？\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78Vk1QEicCLX3Fias21ltfY0BB5Ng8PZWmaPhgN4iaTMK7wBmEaAgbFldNQ/640?wx_fmt=jpeg)\n\n\n\n我们换个思路， 在点击刷新按钮的时候，右键网页-查看元素-网络，我们发现每一次刷新，其实就是代表着这一个post请求，那么只要我们将这个post请求的内容用Python发送出去，不就达到我们的目的了吗\n\n\n\n那么\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78SPZTq20epkZbdvFYXyiaygYwbsjrFa0OTkbtDeO9Z9iaYCKAsn7xgbeg/640?wx_fmt=jpeg)\n\n1.首先，用Firefox浏览器打开以下的页面：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78yWu5ibgZhhOEaAgENlVdctDscpARS69TNMFjwIygibfF2E7rrBMMBbuw/640?wx_fmt=png)\n\n\n\n2.右键空白处-查看元素-网络，然后点一下刷新按钮，在里面找到这个post\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78MzeKhYhRboxg7vAOY3GRdDnAvOIYGKr83a6c4c95Fta3Qbt2G5MDBQ/640?wx_fmt=png)\n\nhttps://wabi-mc-sha-redirect.analysis.chinacloudapi.cn/powerbi/content/packages/xxxxxxxx/refresh/\n\n这个网址就是让powerbi刷新的post请求，packages后面的数字替换成自己的就ok了，但是这个网址可不是直接复制到地址栏按enter就行的，因为这不是get请求，所以会得到这个结果。\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78NEmSscFNwHQAJcH0bya2Rx1JVx3X6EQw2UYMPM2seA9FqI9Vf2icwbw/640?wx_fmt=png)\n\n那应该怎么办呢？很明显要用Python构建一个POST去请求了。\n\n\n\n3.点击这个post链接，查看消息头\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78Ea4FLrcVDyjKPhV9ZicBJb42HwVYQiaa7420kDvBQ1ulaZvHCQkfScoA/640?wx_fmt=png)\n\n\n\n4.点击编辑和重发（注意先不要点击发送）\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78DicdVYxbiaJBGrRicHyJUicLReYVPgCPTFGzfPvoHP1nRMoj3GuY7zrg0A/640?wx_fmt=png)\n\n上图我们可以得到需要POST的网址和请求头内容，也就是用python来模拟浏览器的方式，包括cookies\n\n5.开始Python大法\n\n首先是需要用到的库，Requests是用Python语言编写的，基于urllib3来改写的，采用Apache2 Licensed 来源协议的HTTP库。\n\n\n\nimport requests\n\nrequests用法特别简单，refresh_url为以上获取的刷新链接，直接用requests.post请求这个链接即可。\n\n\n\nrefresh_url= 'https://wabi-mc-sha-redirect.analysis.chinacloudapi.cn/powerbi/content/packages/xxxxxxx/refresh/'\nresponse = requests.post(refresh_url)\nprint(response)\n\n打印一下响应，发现得到的是<Response [403]>，登录错误，看一下我们的代码，没有任何登录的信息，肯定是无法刷新的。\n\n这里我们就加上请求头内容，请求头里包含了很多信息，其中就有包含登录信息的cookies，还有一些编码信息。\n\n好，接下来我们直接将原网页的请求头复制下来，到python中，当然，需要注意格式，手动编辑一下。\n\n\n\nheaders = {\n'Host': 'wabi-mc-sha-redirect.analysis.chinacloudapi.cn',\n'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',\n'Accept': 'application/json, text/plain, */*',\n'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n'Accept-Encoding': 'gzip, deflate, br',\n'ActivityId': 'xxxxxxxxxxx',\n'RequestId': 'xxxxxxxxx',\n'Authorization': 'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxx',\n'Content-Type': 'application/json;charset=UTF-8',\n'Origin': 'https://app.powerbi.cn',\n'Connection': 'keep-alive',\n'Referer': 'https://app.powerbi.cn/groups/xxxxxxxxxx/list/datasets?tenant=xxxxxxx&UPN=xxxxxxx@xxxxxxxxxx',\n'Content-Length': '0',\n'TE': 'Trailers'\n}\n\n然后在POST语句中添加headers=headers这个参数，这样就把请求头的内容放进POST中了\n\nresponse = requests.post(refresh_url,headers=headers)\nprint(response)\n\n再打印一下响应，<Response [200]>，ok，搞定！\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78GjlMT0j2naK7icUtibeMf1LVibS0fdJneRKhrIE5wdbTzHibwoK3YQhSBA/640?wx_fmt=png)\n\n\n\n最后这条就是刚刚完成的POST刷新。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78a6MFX7KkxRFn0Vvune1BWXOjStjibFDO9u0HUp8U0klJ6wYd0d721oA/640?wx_fmt=jpeg)\n\n接下来还是每10秒刷新一次，并且加上一个刷新的时间记录，并打印出来，以便我们随时观察有没有什么问题。\n\n\n\nfrom datetime import datetime\nwhile True:    \n\tprint(datetime.now())      \n\tresponse = requests.post(refresh_url,headers=headers)    \n\tprint(response)    \n\ttime.sleep(10)\n\n这样，我们就又完成了操作。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78cevToftHmOoJPriamfBuGJiay4QQtngfdnpYYiajKPRaQaeWYxhchEYpg/640?wx_fmt=jpeg)\n\n\n\n\n\n偶尔观察一下打印结果，每次都是<Response [200]>，应该是没问题的，可以观察一段时间。\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78klqGUtcia2jEqE8ywfKpzYO3I6DEQYQEbTye5lnyrujIOLGv71iapVLw/640?wx_fmt=png)\n\n\n\n以下是刷新纪录\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G786AwnBymZHlcricFrMYfjxQbvKIv6UpvWEfBamkRpA8MS2MboQGeXCSg/640?wx_fmt=png)\n\n运行代码，葛优躺喝咖啡，甚至可以抽空来个大保健，美滋滋。\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78t3M7lpbYGCM81Z4GORa6QoGvrroWjE8vqdVQictms7hwlibicG6DxicYQQ/640?wx_fmt=gif)\n\n\n\n\n\n好了，做个总结，我们来对比一下今天讲的response方法和上一篇讲的selenium模拟刷新的优缺点：\n\n用selenium登录Firefox模拟点击的办法很方便，而且能够肉眼看见刷新，也不影响用户对电脑做其他操作，只不过对于配置较低的电脑会造成占用内存较大的问题；\n\n使用response来POST刷新链接，比selenium更进一步，甚至不需要打开浏览器，全部操作都是在后台进行，几乎不会占用内存，几乎对用户无任何影响。\n\n\n\n那么还是那个问题，如果雇一个人7d×24h不简单每10秒刷新一次Power BI，我需要每月支付他多少钱？\n\n\n\n完整源代码请关注本号【学谦数据运营】回复“1”获取。\n\n————————\n\n留一个悬念，用response来POST刷新链接有一个问题，就是每当刷新一小时后，就会再次出现401错误，为什么呢？\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78B9CDeYlbAib4s287zSOlvt57c4UhfL6EzcuNj6e54NZceicsYgLVBKUQ/640?wx_fmt=png)\n\n因为powerbi这个网页虽然一直保持登录状态，但是cookies里的Authorization已经发生了变化，比如最开始的时候结尾是……QEgilBRFwTX3ZKUSA，但是过了一段时间，变成了……pyMyPKkznf1bAKSSVg。\n\n所以cookies发生了变化，肯定登录就失效了，登录失效了，自然无法刷新，response也就不是200了。\n\n那么问题就来了，如何得到最新的Authorization呢？\n\n\n\n这就是下一篇内容的事情了，请关注本号【学谦数据运营】，等待下次更新。\n\n![image-20200410081747082](https://tva1.sinaimg.cn/large/00831rSTly1gdocxfo2utj30rc0ogmyt.jpg)"
  },
  {
    "path": "_posts/2020-03-19-【运营】新用户数量？Power BI简单三步计算.md",
    "content": "【运营】新用户数量？Power BI简单三步计算\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXulbreLhID9zVs4DYPciaYiaeZqjL9Uo1UoticvJ9KAY9vJAIoY6ARR3PA/640?wx_fmt=png)\n\n今天开始，给大家讲一讲在运营工作中经常用到的几个数据：\n\n新用户数量\n\n复购用户数量\n\n沉睡客户数量\n\n激活客户数量\n\n流失客户数量\n\n日活、周活、月活\n\n……\n\n等等指标\n\n\n\n今天先来谈一谈新用户：\n\n拉新招新一直是各大企业业绩增长的命门，在维护好老用户，提升口碑的前提下，新用户的注入无疑会提升各项业绩。\n\n但是如果从大量的订单中筛选出新用户的订单，尤其是要进行按月、按周进行分析时，该项工作靠excel表去计算无疑工作量十分繁重，而使用powerbi来计算时，我们只要编写几个度量值就可以一劳永逸地解决问题。\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkX5UjTylBad8VGZvvwXWtkyrUQ4XaltRRI74YtMjYX22aBo0qiaJ1CzUw/640?wx_fmt=png)\n\n用户增量方式：\n\n1、找到目标用户，了解你的用户的真正需求是什么。让其他的销售人员也成为你的用户\n\n2、根据用户需求找出用户痛点。销售人员的痛点就是利润返点\n\n3、帮用户解决实际需求和痛点问题，真正帮到用户，然后让用户口碑传播，这样增加的用户才能有粘性\n\n举个栗子：\n\n你的去找一个公司跟你合作，不用直接找他们老板，直接找他们的销售人员，不是让他帮忙你销售。是让他帮你引荐他的朋友或者客户，如果成交了，第一单的利润全部给他，你一分钱不赚。\n\n然后你接着维护这个客户两个月，三个月。。。\n\n如果这样的话，你是不是一分钱的成本都没有投入，也没有冒很大的风险，你每个月都有免费的客户送上门，这样你牺牲的只是第一个月的利润，换来的却是源源不断的客户\n\n\n\n以上业务层面的问题，操作起来当然需要十分强大的运营能力，除此之外，不管是业务运营人员还是数据分析人员，都需要明确知晓各月的新用户数量以及新用户占比。那么我们用Power BI如何快速地计算出各月新用户数量呢？\n\n\n\n仔细考虑其实比较简单，分为三步：\n\n1. 计算每一个用户首次购买的时间\n2. 判断该用户首次购买时间是否落在我们选定的日期范围\n3. 如果是，那么他就是新用户，count+1，或者输出明细即可\n\n\n\n当然，计算之前我们需要首先创建一个日期表，关于如何创建日期表，参考这篇文章：[Power BI创建日期表的几种方式概览](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483782&idx=1&sn=c756367adfa92bfa0fedb6674e369aa6&chksm=ea674567dd10cc71248d01ad6d7bac06994753c9273a110b60ad4e75f233400a4603b102c6c2&scene=21#wechat_redirect)\n\n\n\n以下是数据格式：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXhAhTtGfuTEYVeRefSh5dMuAxdbM8wnrOIbNIxciafYcfCCaudsKG2hA/640?wx_fmt=png)\n\n\n\n我们用订单日期和客户ID来作为关键的列，直接给出度量值：\n\nNEW CUSTOMERS = \n\nVAR customer_firstsale=  \n\nCALCULATETABLE(\n\n​    ADDCOLUMNS(\n​      VALUES(sales[客户ID]),\n​      \"FIRSTSALE\", \n​     CALCULATE(MIN('sales'[订单日期]))),ALL('日期'))\n\nVAR customer_first_sale_in_current_period=  \n\nFILTER(\n    customer_firstsale,\n    [FIRSTSALE] IN VALUES('日期'[日期]))\n\nVAR RESULT=  \n\nCOUNTROWS(customer_first_sale_in_current_period)\n\nRETURN RESULT\n\n这里用到的是表函数的用法，将表作为筛选器，结构上更加清晰一些，当然，你也可以使用CONTAINS函数来计算：\n\nCONTAINS(VALUES ('日期'[日期]),'日期'[日期], [FIRSTSALE])\n\n为了对比该月的总用户数，我们也写一个度量值：\n\nCUSTOMERS = DISTINCTCOUNT(sales[客户ID])\n\n放在矩阵中显示，再添加一个新客户占比：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXdRiacg1syYSdliba7gAibQGeAg7ia6ia8A5ZP39D7DJSibQXwtA6Rf7nTS8w/640?wx_fmt=png)\n\n\n\n我们发现，第一个月新客户占比是100%，这是很显然的，第一个月购买的客户的购买日期一定在第一个月内。随着业务发展，老用户沉淀，新用户占比会越来越低，也符合业务发展规律。\n\n\n\n在总计行用户和新用户都是790，这是因为总计行和每一行的计算方式不同，总计行忽略了月份，总共就一个日期范围，因此这两个数是相同的。\n\n\n\n在实际业务中，运营人员可能需要查看具体的每个月的新客户的名单，重点去跟进，那么这个需求我们如何满足呢？\n\n\n\n也比较简单，只不过返回的不是COUNTROWS，而是一个明细，预知后话如何，请关注本号，查看后续内容。"
  },
  {
    "path": "_posts/2020-03-19-【运营】新用户明细？Power BI一招帮你搞定.md",
    "content": "![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXK4QaLAMz4gKyh1eex9kKz6xeLX0vKaDYml2gQ0Q3XQhF7nvE51sR7g/640?wx_fmt=png)\n\n上一篇文章中我们讲了如何计算新客户的数量，但是在实际业务中，运营人员可能需要查看具体的每个月的新客户的名单，重点去跟进，那么这个需求我们如何满足呢？\n\n\n\n其实也比较简单，只不过返回的不是COUNTROWS，而是一个明细，我们使用的是CONCATENATEX函数：\n\nCONCATENATEX函数的具体用法是：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkX6T73yNk5uVKe5e6o5HmV1HbSpf0lZ5Dicv98FctgeBoyVlpY1OUgTjg/640?wx_fmt=png)\n\n具体度量值直接给出：\n\nNEW CUSTOMERS LIST = \nVAR customerfirstsale=\n  CALCULATETABLE(\n    ADDCOLUMNS(VALUES(sales[客户ID]),\n    \"FIRSTSALE\",\n    CALCULATE(MIN('sales'[订单日期]))),ALL('日期'))\nVAR customerfirstsaleincurrentperiod=\n  FILTER(\n    customerfirstsale,\n    [FIRSTSALE] IN VALUES('日期'[日期]))\nVAR RESULT=\n  CONCATENATEX(\n  customerfirstsaleincurrentperiod,\n  [客户ID],\n  \"、\")\nRETURN RESULT\n\n\n\n放在矩阵中，就可以直接显示了：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXPoXoFIU8FXcJ6q0moj1t9UrA9ohjaKoxia4SmKMvohs2w21F3zgibn0w/640?wx_fmt=png)\n\n注意如果要显示明细，最好行小计不要显示，因为总计行对于明细来说没有任何意义，它会将里面的所有790个用户都显示在一个格中。\n\n但是关掉行小计，我们就没法直观地看到总的人数了，怎么办呢？\n\n别急，我们还是有办法的，对于行上的显示，我们可以对度量值进行一定的修改，将最后一行改为：\n\nRETURN IF(HASONEVALUE('日期'[Y-M]),RESULT)\n\n这句话的意思是：如果检测到有'日期'[Y-M]筛选器，就显示result，否则不显示，也就是在总计行不显示，结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXpM04SSAQF1TtwUUwXE8TVBzCxVzpjFsZ72g4TJAWibibZxX3yDpYsDicQ/640?wx_fmt=png)\n\n\n\n这样就两方面兼顾了。\n\n你学会了吗？\n\n\n\n下一篇我们介绍如何计算流失客户数量与分析。\n\n更多PowerBI教程，清关注本号，持续更新。\n"
  },
  {
    "path": "_posts/2020-03-20-如何显示数据更新时间.md",
    "content": "【PowerBI技巧】如何显示数据更新时间\n\n在某些场景中，我们需要告诉用户，报表中的数据是截止到昨天？截止到今天上午？2小时之前？还是10分钟以前的，这就需要在报表中加入如下的内容：\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtc0cjwL8Xltcj7befVuvpztHFZM3AicLHAELJIIVhicaibGHqPDLnBbia8g/640?wx_fmt=png)\n\n今天就和大家来讲一下如何实现以上的功能。\n\n\n\n我们很容易想到，在DAX语言中有一个NOW函数，用来获取当前的日期和时间：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtezoWRwHhbIs12lH5qLoswnIKicN5gp2h0u4FkgdAYQ5LCqqCwy3zHdw/640?wx_fmt=png)\n\n\n\n我们来测试一下，输入公式，得到数据：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtgFSVhOSy7ibjCcbNt6ZJMu2s1bkPibNKIMkIc0PQ8f3XCO0jNmqoSicgQ/640?wx_fmt=png)\n\n\n\n用卡片图呈现出来：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtd4JvjXDAQ9fsh1ibTibOagWJoY5BMYjaQ2YrOicSciaAPBnudMsjbjyNmg/640?wx_fmt=png)\n\n点击刷新，可以看到每次刷新数据，都会更新一个最新的时间。\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtTPypseJm1iaw8M9va6FwEOW1icknZib8vdS84qn07SicC9n9MIm6Xkuicvw/640?wx_fmt=gif)\n\n将报表发布到云端，再来查看一下。\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtBmh4s5icaSdtOFeicXqhcIFqCOichmWyqbxznu9ERB3ic1IGzcnktuMvSw/640?wx_fmt=png)\n\n\n\n没有问题。\n\n但是！如果到云端进行刷新，就会发现时间变为了8点多，跟发布的时间很明显是不一致的，为什么会这样呢？\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtd11IuRKR1loHPsIrmJYdHU3EGXXI5WUOeIynrOomtn3s4OVDxSx7UA/640?wx_fmt=gif)\n\n\n\n因为powerbi本地刷新和云端刷新是不同的，本地刷新，NOW返回的是当前的系统时间，也就是UTC/GMT+08:00时间，而云端刷新的时间是按照UTC时间来的，所以两者差了8个小时。\n\n\n\n所以如果想在云端刷新时显示正确的当地时间，应当在原来的时间上+8小时，但是这样一来，又会出问题，那就是如果修改本地文件并再次发布时，时间就会比当前早8个小时。\n\n\n\n也就是说，使用NOW无法同时满足本地发布和云端刷新的需要。\n\n那应当怎么办呢？\n\n\n\n这时候我们该用到UTCNOW函数了，顾名思义，这表示的是UTC时间的当前时间，这样只要写出如下的表达式，就能正确得到本地的准确时间了：\n\n当前时间 = UTCNOW()+\"08:00:00\"\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtEsbbjnLhRyViczfbvibgHtlpY68W7urzLgBibwbSe0u3Xuj4nwhoAlpiaA/640?wx_fmt=gif)\n\n\n\n再次发布到云端，刷新看一下：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtEngh59wib1TM7EuVzdqIcrLyLNZcSlmB4u6GUWdASwefxhwRCSPd5Yw/640?wx_fmt=gif)\n\n\n\n这样，我们就可以同时在本地和云端分别刷新都得到正确的刷新时间了。\n\n你学会了吗？\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtOUDUicICwT7ibxK8St0njPqwufH28Kapo3uXCcRicGNyo4m42L9JVBhAg/640?wx_fmt=png)\n\n\n\n这里我们需要注意，以上两张gif中，点击网页端报表页面的刷新按钮，仅仅是将数据刷新到数据源中的最新，而不会真的更新数据，因为一旦报表发布后，只要不在数据源中点击立即刷新，报表中的数据是不会变的。\n\n\n\n但，事实真的是这样的吗？且看下图：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtEOgc8uUqTIMrU5lUubhZSOIckNZya0nyu7wgRKzF2HDWj2JeWdAWJQ/640?wx_fmt=gif)\n\n\n\n我们可以看到，在这个gif中，我们点击报表页面的刷新按钮，当前时间是一直在变的，一直显示当前的本地时间，这个是怎么做到的呢？\n\n\n\n还有另外一个问题，就是我们事先知道当地的时区，所以才会在UTC上+8小时，如果恰好不知道时区呢？有没有不需要知道时区就通用的公式呢？办法肯定是有的。\n\n\n\n敬请关注本号，查看后续讲解。\n\n![学谦数据运营LOGO的副本](https://tva1.sinaimg.cn/large/007S8ZIlly1geow8dxh6nj309p08wwf9.jpg)\n\n"
  },
  {
    "path": "_posts/2020-03-21-【运营】沉睡、流失客户分析？Power BI一招帮你搞定.md",
    "content": "【运营】沉睡、流失客户分析？Power BI一招帮你搞定\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj38MKXWZg0vjhlB17C6MkXq3YgHOMKeVlDaghh44ePYOicEVXln2tXrLnsNMb3Q2fUY0oHcuL52VA/640?wx_fmt=png)\n\n上两篇我们讲了如何计算新客户的数量和展示明细\n\n[【运营】新用户数量？Power BI简单三步计算](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483842&idx=1&sn=61ab7df60763e3e61b72676354cf3f17&chksm=ea674523dd10cc358cf27e4c44b6b037d95efb03f23520189580cb2839c5bf04278754411b1b&scene=21#wechat_redirect)\n\n[【运营】新用户明细？Power BI一招帮你搞定](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483853&idx=1&sn=5bef312d354f1c2ad138d814d2df5355&chksm=ea67452cdd10cc3a2efab8b5fb791070c0382e5d20578e58bb3cfef472dbc87ffcd54d7e06ca&scene=21#wechat_redirect)\n\n在实际业务中，新用户很重要，但是如何留存老用户更是一个巨大的课题，总的来讲，就是提升服务质量，增强满意度，具体细分在各个行业，运营思路千差万别，我们今天不详细展开。\n\n\n\n但有一点是几乎所有行业都想通的，就是我们要对沉睡、流失的客户进行回访，分析，想尽办法进行唤醒、激活或重新购买。Power BI如何帮助业务人员进行统计汇总呢？结合新用户的计算方式，我们脑海中大概有一个轮廓：\n\n首先，要定义何为流失，因各家企业对该指标的定义有较大差异，就以6个月内曾经有订单，但最近两个月内没有订单的客户定义为流失客户；\n\n第二步，如何写度量值：\n\n1. 计算每一个客户最后一次订单的日期；\n2. 日期如果落在最近6个月到2个月之间，就是我们想要的流失客户\n\n\n\n直接给出度量值：\n\nLOST CUSTOMERS = \n\nVAR customer_lastsale=\n  CALCULATETABLE(\n    ADDCOLUMNS(VALUES(sales[客户ID]),\n    \"LASTSALE\",\n    CALCULATE(MAX('sales'[订单日期]))),\n    ALL('日期'))\n    //返回每一个客户最后一次购买的时间，为了不被年月筛选器筛选，添加了一个ALL\nVAR BEGINDAY=\n  CALCULATE(\n    MIN('日期'[日期]),\n    DATEADD('日期'[日期],-6,MONTH))\n    //返回6个月之前的第一天\nVAR ENDDAY=\n  CALCULATE(\n    MAX('日期'[日期]),\n    DATEADD('日期'[日期],-2,MONTH))\n    //返回l两个月前的最后一天\nVAR customerlost=\n  FILTER(\n    customer_lastsale,\n    [LASTSALE]>BEGINDAY&&[LASTSALE]<ENDDAY)\n    //返回最后一次购买日期处于该时间段的行\nVAR RESULT=\n  COUNTROWS(customerlost)\n\nRETURN RESULT\n\n结果如下：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFt4Rs1RRoqQwlGtctJOOTX3jyd8TPSWNV8mb3raoXbmxiadfWVka2P2QQ/640?wx_fmt=png)\n\n\n\n最后一列就是该月流失的客户。\n\n根据上一讲列表显示明细[【运营】新用户明细？Power BI一招帮你搞定](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483853&idx=1&sn=5bef312d354f1c2ad138d814d2df5355&chksm=ea67452cdd10cc3a2efab8b5fb791070c0382e5d20578e58bb3cfef472dbc87ffcd54d7e06ca&scene=21#wechat_redirect)，我们也来适当修改上面的度量值，如下：\n\nLOST CUSTOMERS LIST=\n\nVAR customer_lastsale=\n  CALCULATETABLE(\n    ADDCOLUMNS(VALUES(sales[客户ID]),\n    \"LASTSALE\",\n    CALCULATE(MAX('sales'[订单日期]))),\n    ALL('日期'))\n    //返回每一个客户最后一次购买的时间，为了不被年月筛选器筛选，添加了一个ALL\nVAR BEGINDAY=\n  CALCULATE(\n    MIN('日期'[日期]),\n    DATEADD('日期'[日期],-6,MONTH))\n    //返回6个月之前的第一天\nVAR ENDDAY=\n  CALCULATE(\n    MAX('日期'[日期]),\n    DATEADD('日期'[日期],-2,MONTH))\n    //返回l两个月前的最后一天\nVAR customerlost=\n  FILTER(\n    customer_lastsale,\n    [LASTSALE]>BEGINDAY&&[LASTSALE]<ENDDAY)\n    //返回最后一次购买日期处于该时间段的行\nVAR RESULT=CONCATENATEX(customerlost,[客户ID],\"、\")\nRETURN IF(HASONEVALUE('日期'[Y-M]),RESULT)\n\n\n\n结果如下：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtficrs7CwtccA3mqw0o7SeSzzva4jpuY6tpWM9KZWF8YDR1J0z7dBI4w/640?wx_fmt=png)\n\n\n\n这样我们就可以得到各月流失的用户了。\n\n你会发现，有些用户在这个月也流失，在下个月还流失，这是由于不同的企业对于流失的概念定义有区别造成的，其实准确来说应当叫做睡眠。\n\n比如曹娜-18580这个用户，最后一笔订单发生在2017年6月，那么在7月8月未发生订单，他在8月属于睡眠用户。同样，他在8月和9月也未发生订单，所以在9月也是睡眠用户，而到了11月，已经过了6个月内有订单了，他就真的属于流失客户了。\n\n一般情况下，一个用户如果连续6个月没有新订单，再重新有订单的可能已经非常小了。\n\n\n\n当然，还是希望各位运营的小伙伴，永远用不到这个指标。\n\n\n\n在日常的运营管理中，我们经常会遇到想要查看某个时间段的用户在下一个时间段的复购情况，而且时间段是任意的，可以按月，可以按周，可以任意选择时间段，那么这个该如何实现呢？我们下期再说。"
  },
  {
    "path": "_posts/2020-03-22-【运营】任意两个时间段的复购率？Power BI一招帮你搞定.md",
    "content": "【运营】任意两个时间段的复购率？Power BI一招帮你搞定\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtrhicJ8LSD2k2iaau8sEX3ggXJxQfSaeGicaNv5qnHCian1eeY7pk1ibnQjQ/640?wx_fmt=jpeg)\n\n前面几讲内容，我们分别介绍了新用户和流失客户的分析\n\n[【运营】新用户数量？Power BI简单三步计算](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483842&idx=1&sn=61ab7df60763e3e61b72676354cf3f17&chksm=ea674523dd10cc358cf27e4c44b6b037d95efb03f23520189580cb2839c5bf04278754411b1b&scene=21#wechat_redirect)\n\n[【运营】新用户明细？Power BI一招帮你搞定](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483853&idx=1&sn=5bef312d354f1c2ad138d814d2df5355&chksm=ea67452cdd10cc3a2efab8b5fb791070c0382e5d20578e58bb3cfef472dbc87ffcd54d7e06ca&scene=21#wechat_redirect)\n\n[【运营】沉睡、流失客户分析？Power BI一招帮你搞定](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483892&idx=1&sn=2c6cdbda3b9158fc73abd6ec0f5c5acc&chksm=ea674515dd10cc03e86a043939fe5eca94795898ce38e3fb84ab333de4dcf41ab310f3d93308&scene=21#wechat_redirect)\n\n\n\n在日常的运营管理中，我们经常会遇到想要查看某个时间段的用户在下一个时间段的复购情况，而且时间段是任意的，可以按月，可以按周，可以任意选择时间段，那么这个该如何用Power BI实现呢？\n\n\n\n我们先整理一下思路：\n\n既然是任意选择时间段，那么切片器一定是直接用日期切片器，选择范围。\n\n前一个日期范围和后一个日期范围，所以需要同时有两个切片器。\n\n那么问题来了，我们知道同一个字段的切片器相互之间是有影响的，所以一个日期表是不能解决问题的，我们需要第二张日期表。\n\n日期表2 = '日期表'\n\n新建表-输入以上内容，就这么简单，它会复制日期表的全部内容到日期表2中。\n\n同样，日期表的日期字段也要和订单表建立关联：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtG2iaw5f8m66otdpVvq8DygkAt0FUD3toU8cM6NTpSjLaFWSUNqt8xOw/640?wx_fmt=png)\n\n\n\n我们将两个日期字段都添加为切片器：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtpibibSCib5ianSQUkIibstujibJQwRTbNCIXWCEGquCJ7bvPMNClPG4W98hg/640?wx_fmt=png)\n\n我们要做的就是添加几个度量值：\n\n1. 日期1范围的客户\n2. 日期1范围的客户在日期2中也产生了订单\n3. 以上两个的百分比\n\n\n\n我们直接给出度量值：\n\n日期1的客户数量 = \nCALCULATE(\n  DISTINCTCOUNT(sales[客户ID]),\n  ALL('日期2')\n)\n\n\n\n日期1的客户在日期2中复购的数量 =\n\nVAR CUSTOMERSINDATE1=\n  CALCULATETABLE(\n    SUMMARIZE(sales,sales[客户ID]),\n    ALL('日期2'[日期]))\nVAR CUSTOMERSINDATE2=\n  CALCULATETABLE(\n    SUMMARIZE(sales,sales[客户ID]),\n    ALL('日期'[日期]))\nVAR REPEATCUSTOMERS=\n  INTERSECT(CUSTOMERSINDATE1,CUSTOMERSINDATE2)\n\nRETURN COUNTROWS(REPEATCUSTOMERS)\n\n\n\n复购率% = DIVIDE([日期1的客户在日期2中复购的数量],[日期1的客户数量])\n\n\n\n这里用到了一个新的函数：INTERSECT\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtibklyODO1GgZG9ssvia6xKCTrlmPY8ia0uGfSyiaxwceibZMMczlaltnneA/640?wx_fmt=png)\n\n\n\n根据函数的描述，也就是求两个表的交集。再用COUNTROWS计算多少行，就是复购的数量，再除以日期1的客户数量，就得到了【复购率%】。\n\n放到矩阵中：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFttmA2mlFVxC8DfCXa60FIR13hf0C468xH9hIwWPSPDxsURberrTibXicQ/640?wx_fmt=png)\n\n\n\n这样，我们随意拖动两个滑竿，就能实现按年、季度、月、周等任意时间段的复购情况。\n\n如果想查看明细，可以添加一个客户ID的字段来下钻：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtSohCg9Xqyy4nD9D9qadG8dtVFH9HgibezYm3CianFZx0QFiajf6cpUOBw/640?wx_fmt=png)\n\n\n\n\n\n在查看不同维度的复购率时，发现了一个有趣的事情：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFticdsMriaH2edTQzsvCb6hw5wxWiaqMIIbqqK0hcm4oesUmhZvVQoryetw/640?wx_fmt=png)\n\n\n\n当左侧切片为地区时，发现只有华东和中南复购分别为2和3，总和应该为5，但是总计行是20，20也是用类别做切片器时的总和，这是怎么回事呢？\n\n\n\n还是我们之前说的，总计行的计算方式与其他行是不太一致的， 也可以说是完全一致的，为什么这么矛盾呢？\n\n\n\n因为普通行受到本行的切片器影响，所以华东地区只查看华东地区内的复购，中南只看地区内的复购，有可能会发生华东地区的客户下一次在华北地区购买，这样，这笔订单，既不属于华东的复购，也不属于华北的复购。\n\n\n\n然而总计行，是忽略地区切片器的，不管你在哪个地区购买，在哪个地区继续购买，都是复购。\n\n\n\n说总计行和普通行完全一致是因为度量值完全一致，说不一致是因为切片器不同。\n\n\n\n我们修改一下度量值：\n\n日期1的客户在日期2中复购的数量 =\n\nVAR CUSTOMERSINDATE1=\n  CALCULATETABLE(\n    SUMMARIZE(sales,sales[客户ID]),\n    ALL('日期2'[日期]))\nVAR CUSTOMERSINDATE2=\n  CALCULATETABLE(\n    SUMMARIZE(sales,sales[客户ID]),\n    ALL('日期'[日期]),\n    ALL(sales[地区]))\nVAR REPEATCUSTOMERS=\n  INTERSECT(CUSTOMERSINDATE1,CUSTOMERSINDATE2)\n\nRETURN COUNTROWS(REPEATCUSTOMERS)\n\n\n\nCUSTOMERSINDATE2添加了一个ALL(sales[地区])，也就是不受左侧的类别切片器控制，这样就可以显示正常了：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFt2aA5gSZj13J1jaL3ETMXeSy67iaJDLXYPJ0EqBBsHAkGmPLnzxSyaaQ/640?wx_fmt=png)\n\n\n\n这时候，仍然是总计行20不等于各行相加22，因为毕竟有重复，是符合业务逻辑的。\n\n\n\n有时候我们不仅关心客户本身的复购，更关心客户购买产品的复购，即虽然客户A在下一个时间段复购了，但是他在前一个时间段购买3种类别，在后一个时间段只购买了1种类别，我们也需要相应关注，比如：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaeO5nAvvqqLxibY7H2iaBGFtJvr4ps4HF5VVAvY2OLSxYV5HGZOVqk7L845g6wJPWTFlmvaWJGF23g/640?wx_fmt=png)\n\n问题来了，比如第二行的戴虎-14065显示复购，但在复购的类别中却是空的，为什么呢？\n\n\n\n详细解答，敬请关注本号，咱们下期再见。\n\n\n\n预祝各位运营小伙伴，各自岗位的用户复购率为100%！"
  },
  {
    "path": "_posts/2020-03-26-「强强联合」在Power BI 中使用Python（1）——导入数据.md",
    "content": "[「强强联合」在Power BI 中使用Python（1）——导入数据](http://toutiao.com/item/6809844428987957772/)\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82l5jJaadCCfQITRcKHc5gBJxseuLKWibuwl2IsBLNbYEJoXuawYSq9Gdw/640?wx_fmt=png)\n\n近几年，Python是越来越火了，就连地产大佬潘石屹都在年近不惑之时开始学习Python编程语言，我们做数据分析和运营的怎能不熟练运用呢？\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOiaibqWZ7QG4T1lpAxic1jsOMxZZTxYNlIjw2DVqhz92wLv7Jpgm0SuEzKd6b24VzNbvp738XlYk0Gibg/640?wx_fmt=jpeg)\n\n\n\n关于Python的教程，网上铺天盖地，9块9的，99的，999的甚至几千的上万的都有，妖魔鬼怪，乱象丛生，我们这里不去深究，因为Python作为一门胶水语言，各个方向都有成千上万的各种库，发展路径太多了。但是将Python和Power BI组合起来用的还真不多。\n\n\n\n那么，我们今天就来讲一讲Python和Power BI组合起来使用，都有哪些场景。我列了如下三种：\n\n![image-20200513153944998](https://tva1.sinaimg.cn/large/007S8ZIlly1geqv5cjsdsj31bm0q4kjm.jpg)\n\n其中，关于第三种的Power BI的网页端刷新，完全突破Power BI的网页端刷新每天8次的限制，达到7×24任意时间、任意次数刷新，全方位满足您的需求，请查看以下两篇文章：\n\n\n\n[如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483812&idx=1&sn=1d99a342ad280eac128845382886debb&chksm=ea674545dd10cc534548c85c923390ceddb1ea3688d8212f0c5697dbfe863183c52cd1e91128&scene=21#wechat_redirect)\n\n[如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？【2】](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483832&idx=1&sn=57165f8f1e91e463a26589e4652acc88&chksm=ea674559dd10cc4f57a04e48cf7696e54ae150e3a8df155489bdfc8ed69a67350d585f92848f&scene=21#wechat_redirect)\n\n\n\n今天我们主要来讲讲第二种应用：**直接在Power BI中使用Python。**\n\n\n\nPower BI 2018年8月8日的更新已经支持Python了，和之前支持R语言一样。之前接触过Power BI和R语言联合使用的朋友上手应该会快一些。\n\n\n\n那么Power BI 中如何使用python呢？主要有以下5个地方：\n\n![image-20200513154007813](https://tva1.sinaimg.cn/large/007S8ZIlly1geqv5qplt6j31c00qmkjm.jpg)\n\n想要在Power BI 中使用python，我们需要先配置环境：\n\n\n\n1、首先需要安装Python的运行环境，我在电脑中直接安装的的是Anaconda3，关于该包，大家自己在网上找来装吧，或者如果你安装了Visual studio2017的话可以通过VS的安装程序来配置：\n\n![1](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lRTITm3B9HZ352sG11piaRKDiaASR7EXkicd6lrRAf5SxT3JllKaSj5ricg/640?wx_fmt=png)\n\n\n\n2、如果你是直接安装了Anaconda，那么就不需要自己再单独安装pandas和matplotlib包了，因为这些常用的包anaconda早就给你配置好了，因此建议大家在学习Python的时候尽量直接安装anaconda，否则你还需要自己安装这2个包，打开cmd窗口：\n\npip install pandas\n\npip install matplotlib\n\n3、默认情况下Power BI Desktop打开后是无法使用Python进行数据处理和绘图的，如果需要该功能，还需要对Power BI Desktop进行配置。\n\n\n\n依次选择“选项和设置/选项/Python脚本编写”，配置Python3所在的目录位置，我这里是安装在C:\\programdata\\Anaconda3目录下的，点击确定即可：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lhUyIM614g4QGTF98XQF3hGoWEKgP9SJ3ib9TYzVv29QM3fmwroIhL6A/640?wx_fmt=png)\n\n\n\n此外，我们可以使用Python自带的IDE或者安装第三方编辑器，比如PyCharm、Spider。如果使用第三方编辑器，应该做一些基本的配置，限于篇幅，这里不详细展开。\n\n\n\n4、Python与Power BI的数据传递---Dataframe\n\n\n\nPython支持5种常用数据类型，Power BI的M语言支持多种数据类型，两种语言直接以DataFrame数据类型进行传递。由于Python本身并没有支持DataFrame，因此Python会自动调用Pandas库。\n\n\n\nM将其Table类型的数据传递给Python，Python会自动将Table转换为Dataframe；Python的处理结果以Dataframe形式输出，M会自动将Dataframe转换为Table格式。\n\n\n\n\n\n好了，清楚了以上的配置，接下来我们就可以实操演练：\n\n![image-20200513154051377](https://tva1.sinaimg.cn/large/007S8ZIlly1geqv6hs4hvj31bo0qakjm.jpg)\n\n数据获取环节可以通过以下2种方式：\n\n一、图形界面里找“Python脚本”选项；\n\n二、空查询中使用Python.Execute()函数\n\n\n\n我们首先看第一种运行方式：\n\n\n\n1、在首页-获取数据或者Power Query编辑器中依次点击“新建源/更多…”，随后依次选择“其它/Python脚本”，点击确定按钮，显示Python脚本编辑窗口：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lk1BVtVsUCicNvXYMS6oUiamcyicicKIbiasdy3bfl2NqcoheHvoW17Dkusw/640?wx_fmt=gif)\n\n\n\n在Python脚本窗口我们就可以将编写好的脚本粘贴并运行了。\n\n如前所述，我们一般是先在第三方编辑器中编辑并运行代码无误之后再放到Power BI 中运行：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lAEW9BzBicHg6OI7ibibKNLsx5yRlGibaWWHLg7mRr7zQ7G1K6PNCM1NucQ/640?wx_fmt=png)\n\n\n\n得到结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lqeBdBSnibGY4MQV23j84PCjBxPkesvl6IEkeqm1s0IkZesqNQwABfSg/640?wx_fmt=png)\n\n\n\n*注意：最后一行print(df)并非是必需的，我只是为了在编辑环境里查看下输出的结果而已，在贴到Power BI Desktop的时候并不需要该行。Power BI Desktop会自动获取Python代码中数据类型是DataFrame的变量数据。*\n\n\n\n我们将代码复制到Power BI Desktop的Python脚本编辑器中，并运行：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lLcaf8p0elE3B1l7QX8yfk5e9DphTou2dskFUrCOdhibTnFHwWxAt2aA/640?wx_fmt=gif)\n\n\n\n这样我们就将Python运行的结果在Power BI 中显示了。\n\n\n\n接下来我们来看第二种方式，直接在空查询中运行函数Python.Execute()函数：\n\n\n\nM语言中调用Python的主要函数是 Python.Execute，大家可以看看其基本语法：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaibqWZ7QG4T1lpAxic1jsOMxZuQVnVDZI21FUM9Ishoaqia5BlJ2FW1xFdorDkod0YUEL5OFDDGjmDQ/640?wx_fmt=png)\n\n\n\n1、在Power Query管理器中依次点击“主页/新建源/空查询”，公式编辑栏输入Py（注意M语言强调大小写），将会自动出现M函数列表智能提示：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lEKf38ia797SduHGzcLNpjU7Dswy13IOtCzUMlWnYJ2060QYibWBHZX8w/640?wx_fmt=png)\n\n\n\n2、该函数接受一个字符串参数，所以我们要用成对的双引号，然后再将Python代码粘贴到里面，然后按下回车键，此时会出现“编辑权限”按钮，点击之后，弹出“脚本之行”对话框，点击运行按钮即可：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82loshwqUbNBFppKUCe18ffO4nqnSMgIx4BU1CAeoRWflLRyzaPzsmKqQ/640?wx_fmt=gif)\n\n\n\n运行Python脚本后，Power BI会提取所有数据类型为DataFrame的变量出来，我们上面只有一个变量df，我们改下代码来看看，直接拷贝第一个变量，然后改下2个变量的名字：\n\n`import pandas as pd`\n`import numpy as np`\n\n`df1 = pd.DataFrame(`\n    `{`\n        `'key1': list('aabba'),`\n        `'key2': ['one', 'two', 'one', 'two', 'one'],`\n        `'data1': np.random.randn(5),`\n        `'data2': np.random.randn(5)`\n    `});`\n\n`df2 = pd.DataFrame(`\n    `{`\n        `'key1': list('aabba'),`\n        `'key2': ['one', 'two', 'one', 'two', 'one'],`\n        `'data1': np.random.randn(5),`\n        `'data2': np.random.randn(5)`\n    `});`\n\n\n\n运行一下代码：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82loVMicWLMkNIsJ12ACkm10ZTnqXJNn7164w8eCME3GNzgROY5rgB3Vog/640?wx_fmt=png)\n\n\n\n分别右键-将两张表作为新查询添加即可转换为两张单独的表：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lIjTumtZU6FicNcPKkr5iaYtrr95y4ouIN5OnHQqxL6Ylt8xz9rj3vgWw/640?wx_fmt=gif)\n\n\n\nOK！这样我们就成功地用Python来导入数据了。\n\n\n\n*Python和R语言在Power BI中的应用要求是一样的，数据传递的类型都要求是DataFrame，具体的使用场景和使用要求完全相同，会R的朋友，也可以按上述思路进行操作。*\n\n\n\n本篇文章将Power BI中数据获取环节的Python使用讲解完毕，下一篇我们将继续讲解如何使用Python在Power BI中进行数据清洗。\n\n![image-20200513154130497](https://tva1.sinaimg.cn/large/007S8ZIlly1geqv77bn8rj31c60qkkjm.jpg)\n"
  },
  {
    "path": "_posts/2020-03-27-【强强联合】在Power BI 中使用Python（2）——数据清洗.md",
    "content": "【强强联合】在Power BI 中使用Python（2）——数据清洗\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82l5jJaadCCfQITRcKHc5gBJxseuLKWibuwl2IsBLNbYEJoXuawYSq9Gdw/640?wx_fmt=png)\n\n\n\n上一篇文章我们讲解了在Power BI中使用Python来获取数据的一些应用：\n\n[【强强联合】在Power BI 中使用Python（1）](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483920&idx=1&sn=005dff4caa2c42cf33a0fe2ba130edb9&chksm=ea6746f1dd10cfe7a87d97e1589c11a9a701b771b01041993752821ab999ee3f2f6be92e3da0&scene=21#wechat_redirect)\n\n\n\n这一篇我们将继续讲解如何在Power BI中使用Python进行数据清洗工作。\n\n![image-20200513155130832](https://tva1.sinaimg.cn/large/007S8ZIlly1geqvhlymbwj31bw0qmkjm.jpg)\n\n其实我们仔细看一下场景1和场景2，它们之间是个逆过程，场景1是从Python获取数据传递到Power BI，而场景2是Power BI或者Power Query获取了数据，用python来处理。\n\n\n\n那么这个逆过程应该如何操作呢？话不多说，抓紧上车：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOj0HyV1WmPicWepF16gTHP4tWic1wHOZHEmYGGHIbToUekJ4PWrRIJ9B4GWH6yq4HpHA9goacBN0a3g/640?wx_fmt=png)\n\n\n\n前文我们讲过，Python与Power BI的数据传递是通过Dataframe格式的数据来实现的。\n\n\n\n**Python的处理结果以Dataframe形式输出，M将Dataframe自动转换为Table格式。M将其Table类型的数据传递给Python，Python会自动将Table转换为Dataframe。**\n\n\n\n举个简单的例子：\n\n首先我们进入Power Query管理器界面，通过新建一个空查询，并建立一个1到100的列表，再将其转换为表：\n\n= {1..100}\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82ly7QWzfz9VT0bicEicpYMEXutkIMTQKBXuV5WPCXbmjaEH5zUmicytKwjw/640?wx_fmt=gif)\n\n\n\n然后点击“转换-运行Python脚本”：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lgTYfIwPorh4SrgpfVY2kuf3hibRRkfjoiaDWraxKtftFpaJQpqfBiapgQ/640?wx_fmt=png)\n\n\n\n脚本编辑器中自带一句话：\n\n''# 'dataset' 保留此脚本的输入数据\n\n\n\n一行以“#”开头的语句，在Python的规范中表示注释，所以这句话并不会运行，它的意思是将你要进行修改的表用dataset来表示，也就是说Python是通过dataset变量来访问数据的。\n\n\n\n理论上我们需要在这个地方键入：\n\nimport pandas as pd\n\n以表示我们要使用pandas库，但是Power BI在调用Python时，自动导入了pandas和matplotlib库，所以这一行写不写都一样，我们知道下面的代码是在调用pandas库即可。\n\n在脚本编辑器输入框中输入以下代码：\n\ndataset.insert(loc=1,column=\"add_100\",value=dataset[\"Value\"]+100)\n\ndataset就是源数据表自动换换的dataframe格式数据，“loc=1”代表在第一列数据后插入一列，列名是“add_100”，值是“Value”的值+100，第一行是1，add_100列第一行就是101，以此类推：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lrz9W88Ab580ZoRBE0k0RhT8nFlt0UF3PF12UMOYbXzOuclXQ2t4JiaQ/640?wx_fmt=gif)\n\n\n\n点击运行，得到的是一个子表，将其展开：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lFH54XntsH2cBNia0oadrr0cvBwQGVv3uqdDtTDD59YvtHCvf9YPFR9g/640?wx_fmt=png)\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82liancrweuxaP7kWwC1H775s5wxHt3CIIFq82lFHoweFmVAgtjHicdE4Jw/640?wx_fmt=png)\n\n\n\n准确无误。\n\n\n\n当然，我们也可以继续在这个表里进行一系列操作，比如复制一张表，再创建一个新dataframe表：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lnaUmAfYnZ6v6ibI5VCPC8PEUyKFK4H3bFEQU4goIMgG0iaRIjuBa1tcQ/640?wx_fmt=png)\n\n\n\n运行，得到结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82l8Rm4WAcM5d40nxib5DCBvD24ndxI5F3uFatRZ6CqQFTV20fqRseQ5OA/640?wx_fmt=png)\n\n\n\n再比如，我们想提取数据的某列，比如上面这张表的“key2”列，我们可以点击运行Python脚本，并写入如下的代码：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lI38nIuV4w1KBo0fRU3moWZcBeqSmKKll5CfrNt5VSBoSelsf5cK75A/640?wx_fmt=png)\n\n*（power query自动对Python添加 #(lf) 用来进行转义）*\n\n\n\n当然，以上所说这些功能直接在powerquery中就可以实现，甚至更简单便捷，所以上述内容都是些：\n\n![点击查看源网页](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lXibiaVbwkHtHmiaibs1kPBY1c3GjicPy1huFWv3VwM8PoVaXic8gmHXhJmMw/640?wx_fmt=jpeg)\n\n\n\n\n\n吗？\n\n\n\n并！不！是！以上只是在循序渐进地告诉大家，powerquery中是可以用Python进行数据清洗的，并且清楚地告诉大家调用Python的方法，大家应该很熟练了吧。\n\n\n\n以下才是重点（当然上面也是）：\n\n![点击查看源网页](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lu33zMjkRXZdBMsUyNaUx4pA9Lpib9CdibiclbWXTxmogTYe53X11rJqFA/640?wx_fmt=jpeg)\n\n\n\n在powerquery数据清洗中使用较多的Python功能一定会有正则，因为powerquery本身是没有正则的，所以这时候调用Python来进行正则就显得尤为重要，否则你可能需要在powerquery中添加很多步骤也不一定能得到想要的结果。\n\n\n\n比如下面这个例子：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lScTmdWSE1nR3VNDIiaKCkz30OZTz7IsRh1AB1awuCSdXFicVxgb1fkDg/640?wx_fmt=png)\n\n*真实情况可能远远比这个复杂。*\n\n\n\n这种数据如果已经导入到Power BI中，在powerquery里是没有办法直接进行处理的，这时候就可以调用Python的re正则表达式了：\n\nimport re\nimport json\n\n# 自定义获取文本电子邮件的函数\ndef get_find_emails(text):\n    emails = re.findall(r\"[a-z0-9\\.\\-+_]+@[a-z0-9\\.\\-+_]+\\.[a-z]+\", text)\n    emails=';'.join(emails)\n    return emails\n\n# 自定义获取文本手机号函数\ndef get_findAll_mobiles(text):\n    mobiles = re.findall(r\"1\\d{10}\", text)\n    mobiles =';'.join(mobiles)\n    return mobiles\n\nemail_list=[]\ntele_list=[]\nfor i in range(len(dataset)):\n    text=dataset.iat[i,1]\n    email=get_find_emails(text)\n    email_list.append(email)\n    tele=get_findAll_mobiles(text)\n    tele_list.append(tele)\n    \ndataset['email']=email_list\ndataset['tele']=tele_list\n\n*正则表达式的使用，大家可以进行相关搜索和学习，网上资源还是很多的。*\n\n\n\n这段代码定义了两个函数：get_find_emails（自定义获取文本电子邮件的函数）和get_find_mobiles（自定义获取文本手机号函数），得到两个list，最后再放入dataset数据表中。\n\n\n\n在IDE中运行无误后复制到powerquery的Python脚本编辑器中：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lzw69mfeAyuX8mlCVaYoNSdWWSpZ7NryCNMiaufSSxVTkdMgnRP2jPQg/640?wx_fmt=png)\n\n\n\n点击确定，返回结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lhdZLEMbBwGnqVicPZ2wUW0b6Uzibe8kMfP1auN1zhe6ZIRxEdvtsucew/640?wx_fmt=png)\n\n\n\n后面两列就是我们想要的手机号和邮箱了。\n\n\n\n这样我们就实现了在powerquery中使用正则表达式对数据进行清洗的目的。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78AMoT4SOmNGgzwibEHtMYDvSyib7HEicU4D6EDlsx6PicyniavYZ5oD0qAicg/640?wx_fmt=jpeg)\n\n\n\n*当然，也可以调用R、PHP或者js来实现相同的目的，方法大同小异，各位读者可以自行研究。\n*\n\n\n\n本文讲解了在powerquery中进行数据清洗工作时如何运用Python来实现一些特定的功能。当然，数据清洗的整个流程是复杂多变的，结合本文所讲的内容，希望大家都能充分挖掘powerquery和Python在数据清洗过程中的优缺点，结合起来使用，势必能事半功倍。\n\n\n\n下一篇我们将继续讲解如何使用Python的matplotlib库在Power BI中进行可视化呈现。\n\n![image-20200513154636119](https://tva1.sinaimg.cn/large/007S8ZIlly1geqvch60a1j31c00qekjm.jpg)\n\n\n\n感谢您对本号【学谦数据运营】的关注，支持与厚爱，如果您觉得本文对您有用，请不要吝惜您的点赞、转发、点亮在看，有任何问题欢迎大家在留言区留言，谢谢。"
  },
  {
    "path": "_posts/2020-03-28-【强强联合】在Power BI 中使用Python（3）——数据可视化.md",
    "content": "【强强联合】在Power BI 中使用Python（3）——数据可视化\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82l5jJaadCCfQITRcKHc5gBJxseuLKWibuwl2IsBLNbYEJoXuawYSq9Gdw/640?wx_fmt=png)\n\n\n\n前两篇文章我们讲解了在Power BI中使用Python来获取数据的一些应用：\n\n[【强强联合】在Power BI 中使用Python（1）](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483920&idx=1&sn=005dff4caa2c42cf33a0fe2ba130edb9&chksm=ea6746f1dd10cfe7a87d97e1589c11a9a701b771b01041993752821ab999ee3f2f6be92e3da0&scene=21#wechat_redirect)\n\n\n\n以及如何在Power BI中使用Python进行数据清洗工作：\n\n[【强强联合】在Power BI 中使用Python（2）](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483938&idx=1&sn=a67fdd9eb0cebec3217ae6c5b9739215&chksm=ea6746c3dd10cfd53342278ef91f873901e2fdd71e37894e0843b18b44310b5a2662e7c64faf&scene=21#wechat_redirect)\n\n\n\n这一篇我们继续讲解如何在Power BI中使用Python进行可视化呈现工作。\n\n![image-20200513155143459](https://tva1.sinaimg.cn/large/007S8ZIlly1geqvi0tmdoj31bw0qmkjm.jpg)\n\n\n\n打开Power BI Desktop，在右侧可视化区域会看到一个“Py”的图标，打开该图标,并选择启用脚本视觉对象，拖动字段到“值”的位置：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxe96W6WyzqeQMm7kjusLr2XlrG6twFB87R70IKwbiaJQG1Vd616xQWhvQ/640?wx_fmt=gif)\n\n\n\n添加了字段之后，在Python脚本编辑器中，自动显示了几行内容：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovTZJFemUnjMzbkrRlZe6CalWpb8cwlEyy0wT5qUv90UFENPRgFZRzRAg/640?wx_fmt=png)\n\ndataset = pandas.DataFrame(dead, country, confirm)\n\ndataset = dataset.drop_duplicates()\n\n*注意：这两行代码显示的是被“#”注释掉了，但是在后台有完全相同的两行代码被真实执行了。另外，第二行代码的意思是去重，需要注意。\n*\n\n\n\n为了确保图像能够正确显示，可以在python开发界面将代码调试无误后COPY过来，当然，如果你是大神，也可以在里面直接RUN。\n\n![点击查看源网页](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgNZYnvgSl4jznLias6MeovTS3VQlmz50S36RZIOAjGAWsx6v3jsdPMv7iafh0WNMtfic02x8hkGdTNw/640?wx_fmt=jpeg)\n\n\n\n*反正我是不敢。溜了溜了~\n*\n\n废话不多说，我们直接举两个例子：\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgNZYnvgSl4jznLias6MeovTL3W48JciajEHaHAr3ia9hyhIJRO9YLfwQnReHOhibgbRTB5DroPFmpHeQ/640?wx_fmt=jpeg)\n\n\n\nFirst  one：\n\n在编辑区域输入代码：\n\nimport matplotlib.pyplot as plt\nplt.plot(dataset[\"confirm\"],dataset[\"dead\"])\nplt.show()\n\n\n\n点击运行，发现并没有完整显示数据，且不够美观也不够直观。\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovTsyMTRDKdE5sbddLdW65EiaqSxK433Vh9okH94MIxj0PkuZU5J4ia304w/640?wx_fmt=png)\n\n\n\n这里需要做一些处理，因为“confirm”和“dead”字段默认是以求和的方式显示的，所以只有一个点的数据。\n\n\n\n在可视化的值这里对“confirm”和“dead”字段分别选择“不汇总”。再运行代码，这样出来的就是正常的图形了：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgNZYnvgSl4jznLias6MeovTYCNLLyMWsBDtAz0TXnVPWibk2icfgiclr5y9ibMR0xVGq8rWRNib9GgttLg/640?wx_fmt=gif)\n\n\n\n我们也可以对中间这行代码进行适当修饰：\n\nplt.plot(dataset[\"confirm\"],dataset[\"dead\"],color=\"red\",marker=\"o\")\n\n以获得我们想要的形状和信息：\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgNZYnvgSl4jznLias6MeovTYNRtmPqFeQusdLySRIKkOB5aEGic96H8xRVgSuaEjS11ftsxxrFH8oA/640?wx_fmt=jpeg)\n\n*当然，还是比较丑陋……原谅我的审美。*\n\n\n\n我们再举个美观一点的例子：柱状图。仍然是插入可视化对象-添加字段-输入Python代码：\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\ncams=dataset['cams'].values.tolist()\nIncomePercents=dataset['IncomePercents'].values.tolist()\nplt.figure(figsize=(60,20))\nplt.yticks(fontsize=15)\nplt.bar(np.arange(len(cams)),IncomePercents,label='课收完成率',width=0.8)\nplt.show()\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgNZYnvgSl4jznLias6MeovTfSSoHwAWpoqRFV0roicHtzEhyfibiajYWRXH1OduZCLR7EmMs8giac0zsw/640?wx_fmt=gif)\n\n\n\n结果得到一个很丑陋的柱状图……还不如直接用Power BI做呢！\n\n![点击查看源网页](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgNZYnvgSl4jznLias6MeovTn1vsrzubatnZia2umHHxTicDj3WGnTJ4cIHpQS7yIXPXnv2wuFU4fbNg/640?wx_fmt=jpeg)\n\n\n\n没关系，我们只要按照下面的步骤适当调整一下代码：\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgNZYnvgSl4jznLias6MeovTCGCxkg2iaTtamhCicktLDyl0z1JhF64gcCzEgQcW82hrhZ7IgZDudYbA/640?wx_fmt=jpeg)\n\n就得到了我们想要的结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovTY5tlMfvotU22vbgPv65b2lozlpm293OYf8ibO1wyorjI3EpzrBQD0PQ/640?wx_fmt=png)\n\n\n\n还是乖乖地双手奉上源代码：\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\n\ncams=dataset['cams'].values.tolist()\nIncomePercents=dataset['IncomePercents'].values.tolist()\nplt.figure(figsize=(60,10))\nplt.xticks(np.arange(len(cams)), cams)\n\ncolors=[]\ncam_num=0\nfor cam in cams:\n    cam_num+=1\n    if cam!='整体':\n        colors.append('r')\n    else:\n        break\ncolors.append('g')\ncam_num+=1\nwhile cam_num<=len(cams):\n    colors.append('c')\n    cam_num+=1\n\na=plt.bar(np.arange(len(cams)),IncomePercents,label='完成率',color=colors,width=0.8)\n\ndef autolabel(rects):\n    for rect in rects:\n        height = rect.get_height()\n        plt.text(rect.get_x()+rect.get_width()/2.-0.3, 1.01*height, '{:.1%}'.format(float(height)),fontsize=15)\n\nautolabel(a)\nplt.xticks(rotation=30,fontsize=12)\nplt.yticks(fontsize=15)\nplt.ylabel('完成率',fontsize=18)\n\ncurr_time = datetime.datetime.now()\ntime_str = datetime.datetime.strftime(curr_time,'%Y-%m-%d %H:%M:%S')\nplt.title('2020年03月各公司完成率\\n'+time_str+'数据',fontsize=30)\ndef to_percent(temp, position):\n    return '%1.0f'%(100*temp) + '%'\nplt.gca().yaxis.set_major_formatter(FuncFormatter(to_percent))\nplt.legend()\nplt.show()\n\n*这里面比较复杂的点有两个：一个是添加了整体值，并将高于、低于整体值的部分填充不同颜色，另一个是显示柱状图标签，用到了一个小技巧。*\n\n\n\n当然，以上所说这些作图功能直接在Power BI默认视觉对象中就可以实现，甚至更简单便捷，所以上述内容都是些：\n\n![点击查看源网页](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82lXibiaVbwkHtHmiaibs1kPBY1c3GjicPy1huFWv3VwM8PoVaXic8gmHXhJmMw/640?wx_fmt=jpeg)\n\n\n\n\n\n吗？\n\n\n\n并！不！是！\n\n\n\n还是上一篇的套路，以上举的例子只是简单地让大家认识一下如何在Power BI中调用Python作图，接下来我们介绍一些在Power BI中无法原生作图的例子：\n\n\n\n比如数学制图，绘制sinx和cosx曲线：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovTWKDqsQFhwjFUTEJl5xLKbOJFY51YgCoXZrmWhYHZUicyYWQCO538hOw/640?wx_fmt=png)\n\n\n\n比如绘制子图：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovTmtCVg1ZlT2POANBb0gTnhrogtagaZ8h8M7XweFMgLYhmjDDb30gECA/640?wx_fmt=png)\n\n\n\n比如绘制特殊的柱状图：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovTNxmXDtnDGCoT1yG3ndlydYzbIGOFatWpYYJ0veoUTuaIZSuVicHfBVA/640?wx_fmt=png)\n\n\n\n比如绘制三维散点图：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgNZYnvgSl4jznLias6MeovT1oYgHPqTPbPc0Ptyic6cY8GEH3gWeqcj2rsYT6lhALiaP1rKvb1rdLCA/640?wx_fmt=png)\n\n\n\n等等等等……\n\n怎么样，Python还是有点用处的吧？\n\n更多精彩作图，需要各位结合自己的业务场景，合理选择得到最优解。\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78t3M7lpbYGCM81Z4GORa6QoGvrroWjE8vqdVQictms7hwlibicG6DxicYQQ/640?wx_fmt=gif)\n\n\n\n总结：\n\nPower BI的可视化功能本身就很强大（废话么，人家干什么的），但是毕竟可视化种类不是很多，很多特殊的可视化方法也没有办法直接实现，这时候我们就可以调用Python的matplotlib库进行作图了。\n\n\n\n因为是几乎完全基于Python的作图，Power BI在这里仅起到了图床的作用，所以该部分内容对Python本身尤其是matplotlib库的要求较高，各位读者需要有较强的matplotlib代码编写能力才行。\n\n\n\n好了，本文入门级地讲解了如何使用Python的matplotlib库在Power BI中进行可视化呈现，以补充Power BI自带可视化类型和第三方可视化插件无法实现的功能，想必大家一定能够通过这两个大神级软件的配合使用得到自己想要的可视化呈现。\n\n\n\n——————————\n\n以下是万众期待的下期预告环节：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgNZYnvgSl4jznLias6MeovTCazgvzJicZHIfZd0JmsLw5uUTq2YNObW0BoLFbleLmFGcJZn6MIIYpA/640?wx_fmt=gif)\n\n\n\n众所周知，Power BI对于数据的输出是有一定限制的，至少有这么两个点：\n\n\n\n1.可视化对象导出CSV格式限制3万行数据，这对于数据量动辄上百万甚至上亿的表来说是不可接受的；\n\n2.而一直广为诟病的powerquery数据困难的问题更是一时半会也得不到解决。\n\n\n\n那应该怎么办呢？\n\n\n\n对于第一个问题，我们推荐使用DAX Studio，轻松导出十万、百万条记录。\n\n第二个问题，很可惜没有现成的工具可以直接解决，但是结合本系列《【强强联合】在Power BI 中使用Python》第二篇的内容：\n\n\n\n**Python的处理结果以Dataframe形式输出，M将Dataframe自动转换为Table格式。M将其Table类型的数据传递给Python，Python会自动将Table转换为Dataframe。**\n\n\n\n我们是否可以想到如何用Python将powerquery中的表输出为excel甚至实现回写到SQL中呢？\n\n\n\n这就是下一篇文章要讲的内容了：\n\n![image-20200513155636169](https://tva1.sinaimg.cn/large/007S8ZIlly1geqvmvq9vnj31c60qokjm.jpg)\n\n\n\n感谢您对【学谦数据运营】的关注，支持与厚爱，如果您觉得本文对您有用，请不要吝惜您的点赞、转发、点亮在看，有任何问题欢迎大家在留言区留言，谢谢。\n\n"
  },
  {
    "path": "_posts/2020-03-29-【强强联合】在Power BI 中使用Python（4）——PQ数据导出&写回SQL.md",
    "content": "【重磅来袭】在Power BI 中使用Python（4）——PQ数据导出&写回SQL\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82l5jJaadCCfQITRcKHc5gBJxseuLKWibuwl2IsBLNbYEJoXuawYSq9Gdw/640?wx_fmt=png)\n\n各位小伙伴们，大家好，我是学谦，咱们又见面了。\n\n\n\n《在Power BI 中使用Python》系列的前三篇文章我们分别讲解了：\n\n\n\n如何在Power BI中使用Python来获取数据：\n\n[【强强联合】在Power BI 中使用Python（1）](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483920&idx=1&sn=005dff4caa2c42cf33a0fe2ba130edb9&chksm=ea6746f1dd10cfe7a87d97e1589c11a9a701b771b01041993752821ab999ee3f2f6be92e3da0&scene=21#wechat_redirect)\n\n\n\n如何在Power BI中使用Python进行数据清洗：\n\n[【强强联合】在Power BI 中使用Python（2）](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483938&idx=1&sn=a67fdd9eb0cebec3217ae6c5b9739215&chksm=ea6746c3dd10cfd53342278ef91f873901e2fdd71e37894e0843b18b44310b5a2662e7c64faf&scene=21#wechat_redirect)\n\n\n\n如何在Power BI中使用Python进行可视化呈现：\n\n[【强强联合】在Power BI 中使用Python（3）数据可视化](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483967&idx=1&sn=5c9551c80d9618219838dea0fbedbe80&chksm=ea6746dedd10cfc829141ab2e134ec66d4fb3dd9109c74252bcec48b75192fa5d8350c3c0e20&scene=21#wechat_redirect)\n\n\n\n今天我们继续讲解第四篇——PQ**数据导出与写回SQL**\n\n\n\n众所周知，Power BI对于数据的输出是有一定限制的，至少有以下两点：\n\n\n\n1.可视化对象导出CSV格式限制3万行数据，这对于数据量动辄上百万甚至上亿的表来说是不可接受的；\n\n2.而一直广为诟病的powerquery数据困难的问题更是一时半会也得不到解决。\n\n\n\n那应该怎么办呢？\n\n\n\n第一个问题，推荐使用DAX Studio，轻松导出十万、百万条记录；\n\n第二个问题，没有现成的工具可以直接解决，但是结合本系列第二篇的内容，我们是否可以想到如何用Python将powerquery中的表输出为excel甚至实现数据回写到SQL中呢？\n\n\n\n这就是我们今天要学习的内容：\n\n![image-20200513155636169](https://tva1.sinaimg.cn/large/007S8ZIlly1geqvmvq9vnj31c60qokjm.jpg)\n\n\n\n我们在第二讲中说过：\n\n\n\n**Python的处理结果以Dataframe形式输出，M将Dataframe自动转换为Table格式。M将其Table类型的数据传递给Python，Python会自动将Table转换为Dataframe。**\n\n\n\nM将其Table类型的数据传递给Python，Python会自动将Table转换为Dataframe。那么Python中Dataframe如何输出呢？\n\n\n\n想必了解pandas库的战友们已经想到答案了。\n\n\n\n只要一行简单的代码：\n\n= Python.Execute(\"# 'dataset' 保留此脚本的输入数据#(lf)dataset.to_excel(r\"\"C:\\Users\\金石教育\\Desktop\\abc.xlsx\"\")\",[dataset=源])\n\n\n\n简单吧！\n\n\n\n![点击查看源网页](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgNZYnvgSl4jznLias6MeovTmblzL1Zg57oic0DIazo4GP3xcCdibUE9IibqWUIgboadjkIuLnicXQW3hA/640?wx_fmt=jpeg)\n\n\n\n运行一下：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgNZYnvgSl4jznLias6MeovTYophiaFr3Y7O4CIX6PQic6DVRa29lu1JTnuuukl4mQYmQias5fOqRNomg/640?wx_fmt=gif)\n\n\n\nOK啦！\n\n\n\n关键是：\n\n\n\n只有一行代码！\n\n只要一行代码！\n\n只需一行代码！\n\n\n\n重要的事情强调三遍！\n\n\n\n多年来powerquery广为人们诟病的——数据清洗后无法导出结果的问题就这么被一行代码轻松地解决，美滋滋。\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeVRDn8fY1wyWEydwqMicbMHOQCe8K2CK1MTndaOGwm1AcAYk0PoicO1iaA/640?wx_fmt=gif)\n\n\n\n好了，既然知道了如何导出excel文件，那么各位，写回MySQL数据库的操作是否可以举一反三自行解决呢？\n\n\n\n我们直接看下图的神操作：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeD2lTxxfIMGMayuPnhGt2GBfic4licKBOl53pXLkYk7dXia6fK6uH043ibA/640?wx_fmt=gif)\n\n\n\n看到了吗，mysql数据库中本来是一张空表，我们在powerquery中运行了一段Python代码后，表中有了数据。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOhFSYQRT6gCntGhzicVysOxen5x5xnzO5Zib2piclRHX7aoYrSZBkHsLVtSsvaOp1bgqMbvUlibP51XSQ/640?wx_fmt=jpeg)\n\n\n\n关键代码解释：\n\ndb = pymysql.connect(\"localhost\",\"用户名\",\"密码\",\"nc\" )\n#连接数据库\nquery = 'insert into `全球疫情_country`(id,displayName,areas,totalConfirmed,totalDeaths,totalRecovered,lastUpdated,lat,long,parentId)values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'\n#键入数据\n\nfor r in range(len(dataset)):\n    #按行获取数据\n    id0=dataset.iat[r,0]\n    displayName=dataset.iat[r,1]\n    areas=dataset.iat[r,2]\n    ……  \n    values = (id0,displayName,areas,totalConfirmed,totalDeaths,totalRecovered,lastUpdated,lat,long,parentId)\n    cursor.execute(query, values)\n\ncursor.close()\ndb.commit()\ndb.close()\n#提交数据并关闭数据库\n\n*获取完整源代码，请关注本号【学谦数据运营】，回复关键字“powerbi-python-mysql”\n*\n\n\n\n代码没什么难度，用的是Python的一个常用库：pymysql，将dataset中的数据按行导入MySQL中。\n\n\n\n但是有一个大BUG一点小问题：\n\n\n\n因为全球只有200左右个国家和地区，country层面的数据应该只有200左右。但是，我习惯性地瞥了一眼MySQL右下角，发现：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeuX1dm3WdGv1lWAJzBcfL3ibvecuKoSHqM9I5OYcR2oqeuB9wPdsnhWQ/640?wx_fmt=png)\n\n\n\n难道最近的国际局势变化这么大，已经有567个国家和地区了？不可能吧。抓紧查询一下，发现果然有问题：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOhFSYQRT6gCntGhzicVysOxe84Qgg5EJJuA75cytFJfP75rD1v5IxAO8xybfCiaibGXZWLRPhIia2NVKA/640?wx_fmt=png)\n\n\n\n全球每一个国家和地区的数据都显示了三次，567/3=189，这还差不多。\n\n\n\n而且清空表后再刷新运行，就会发现有的时候是2次，有的时候5次，这意思就是Python代码运行了多次，造成了数据重复，这背后的原因我们无从得知。这样可不行啊，总不能每次使用的时候先去重吧，不太现实，如果有时候疏忽了就麻烦了，那该怎么办呢？\n\n\n\n这个问题先一放，我们来看另一个问题：\n\n\n\n每个国家的每日数据我们只保留一次，即便powerquery每次刷新只向MySQL数据库写入一次，但我们也不能保证编写模型的时候只刷新一次吧，因为一旦人工刷新多次，造成的结果和上面被动造成的结果一致，所以，只要我们解决了人工刷新造成数据重复的问题，查询刷新时被动写入多次的问题也就顺带解决了。\n\n\n\n我们看一下数据，有一列“lastupdated”，是时间格式，也就是查询的时间，由于我们只关心日期数据，因此只取出日期就可以。所以只要每次写回MySQL之前，先判断一下数据库中是否已经存在当日的数据，如果有，就先删除，再将新的数据写入，这样就达到我们的目的了。\n\n\n\n添加以下代码：\n\n#添加一列日期\ndataset.insert(loc=10,column=\"updateday\",value=dataset[\"lastUpdated\"].str[0:10])\n\n#获取日期\ntoday_date=dataset.iat[0,10]\n\n#删除当日的已有数据\nquery_delete='DELETE FROM `全球疫情_country` WHERE updateday='+today_date\n\n\n\n运行一下代码：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxejhh5QEjgVibs05YbSfMX6EE9MwfC8tsCWbU2y9hQY6sFeMiaC03GJk6Q/640?wx_fmt=gif)\n\n\n\nMySQL数据库的表中初始有378条数据（因为包含了3月27日和3月28日两天的数据，共189个国家和地区的数据），运行代码后，仍然是378条，之前已有的3月28日的数据被删除，然后添加了刚刚查询到的最新数据。\n\n\n\n完美解决！\n\n\n\n好了，写回MySQL数据库的全部操作和遇到的问题与解决措施到这里就讲解完毕了。你学会了吗？\n\n\n\n写这篇文章的时候不知道怎么的，远程计算机的MySQL数据库总是出问题，导致我这边文章前前后后写了五六个小时。\n\n\n\n本节内容细节的点特别多，大家一定要自己动手操作几遍，后续我会逐步安排相关的视频讲解，大家请注意关注本号号，跟上队伍。\n\n\n\n------\n\n\n\n以下仍然是下期预告环节：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgNZYnvgSl4jznLias6MeovTCazgvzJicZHIfZd0JmsLw5uUTq2YNObW0BoLFbleLmFGcJZn6MIIYpA/640?wx_fmt=gif)\n\n\n\n下一篇我们将继续介绍一个重磅功能——**数据条件触发预警并邮件通知**：\n\n\n\n说到数据预警，微软自家的Flow可以设置预警条件并发送邮件，这是原生功能，有兴趣的朋友可以去了解。\n\n![image-20200513173700569](https://tva1.sinaimg.cn/large/007S8ZIlly1geqyjd9x2nj31by0qikjm.jpg)\n\n\n\n------\n\n\n\n感谢您对【学谦数据运营】的关注、支持与厚爱，如果本文对您有用，请不要吝惜您的点赞、转发和点亮在看，有任何问题欢迎大家在留言区询问，谢谢。\n\n"
  },
  {
    "path": "_posts/2020-03-30-【强强联合】在Power BI 中使用Python（5）——数据预警与邮件通知.md",
    "content": "【重磅来袭】在Power BI 中使用Python（5）——数据预警与邮件通知\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjmEvtIdpBRzBlEyC4jN82l5jJaadCCfQITRcKHc5gBJxseuLKWibuwl2IsBLNbYEJoXuawYSq9Gdw/640?wx_fmt=png)\n\n\n\n*案例背景*\n\n\n\n*某连锁门店的区域经理助理小朱为当前区域门店创建了多个重要指标看板，但无论是区域经理还是店长，因为日常工作太忙，经常没空细看所有数据看板。小朱希望对于重要指标，特别是有异常的重要指标，可以单独预警。*\n\n\n\n在互联网+时代，一切都讲究“数据化”，而真正用好“数据”，不仅仅是“人看数据”，更要“数据追人”，才能让“数据落地”，如此才称得上将产品/运营/服务等实现“数据化”。\n\n\n\n那么，如何做到“数据追人”，也就是设置数据预警条件，当满足条件时就会有邮件自动提醒呢？\n\n\n\n这就是我们今天要讲的《在Power BI 中使用Python》系列的第五篇内容：\n\n![image-20200513173700569](https://tva1.sinaimg.cn/large/007S8ZIlly1geqyjd9x2nj31by0qikjm.jpg)\n\n\n\n首先我们需要知道的是，Microsoft自家的Flow可以设置预警条件并发送邮件，这是原生功能，但是很多朋友可能并没有使用权限使用Flow，很多连正版的office都没有使用，希望有条件的朋友还是使用Flow比较好，也更简洁方便。\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOhFSYQRT6gCntGhzicVysOxejiaKQBiaGLhpBro879yYU74UeD2o2vwlfa9PrZpWEC8DGd8hibibiamic33w/640?wx_fmt=png)\n\n\n\n如果手头没有Flow的应用，那么只能采取如下的方式了。\n\n\n\n好，我们来看下面的案例：\n\n目前世界各国的新冠病毒确诊人数急剧增加，比如我想知道什么时候美国的感染人数超过15万（截止3.27是10万多了）或者全球的感染总数超过50万（截止3.27已经有42万了），一旦满足条件，我希望邮件能够通知我。\n\n\n\n那么这个想法，Power BI和Python该如何帮我实现呢？\n\n\n\n结合上一篇文章实现的数据导出功能，其实也很简单，只要添加一个判断条件和一个能够发送邮件的库就ok了：\n\n\n\n首先是判断条件的代码：\n\nfor i in range(len(dataset)):\n    if dataset.iat[i,0]=='unitedstates':\n        if totalConfirmed>=150000:\n            #美国人数超过15万的条件满足\n            #以下添加发送邮件的代码\n\n\n\n第二步，能发送邮件的库是smtplib，我们直接测试一下代码：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeAImyAPQUwFmlKt2DePHlYgtMVx2VPfudq5MAoaiaOJ6sG4U6zs1NITA/640?wx_fmt=gif)\n\n\n\n这时，我的邮箱里就收到新邮件了：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeDEeBiaU2cO81ZxkibJ3PXv7VemNvcPnDCwKicO2vicibMBEXV7ibdnKkibeAA/640?wx_fmt=png)\n\n\n\n这样，将条件判断代码和发送邮件的代码组合起来使用，我们就可以实现数据预警和邮件自动发送了。\n\n\n\n不得不再次说一声：\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOhFSYQRT6gCntGhzicVysOxen5x5xnzO5Zib2piclRHX7aoYrSZBkHsLVtSsvaOp1bgqMbvUlibP51XSQ/640?wx_fmt=jpeg)\n\n*获取源代码，请关注【学谦数据运营】，回复关键字“powerbi-python-email”\n*\n\n\n\n有一点需要注意的是：不知道大家有没有认真看收件箱的邮件，同时来了3封相同的邮件，跟上一篇导入MySQL时相同的现象，都是在PQ处理时，Python代码被执行多次的问题（暂时没有查明原因）。\n\n\n\n这个问题其实也好解决，我们将报表发布到云端，再点刷新：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeuPV822xOz1QLTKIBEYNtpFEvP1SueB1sXsotiatNt3gHQzd0uhbDN5g/640?wx_fmt=gif)\n\n\n\n可以看到总共点击刷新按钮3次，每次刷新后增加一封新邮件，加上本身有一封未读邮件，一共是4封。也就是说在云端刷新的报告只会运行一次Python代码。\n\n\n\n而实际上我们发布报表前，理论上设定的条件尚未满足，也就是说不会触发邮件发送。只有当云端刷新几次后满足条件了才会触发邮件发送开关。也就是说，发布到云端就可以解决以上问题。\n\n\n\n小意思。\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOgcmPd6SBjrlvB2EatBRdibj9g43Nq716V6k7E994NN8qjKiaGCTVAjI182Je7PUu8x6nPympnrniatg/640?wx_fmt=jpeg)\n\n\n\n\n\n还有一个问题，不知道你想到没有：\n\n\n\n当美国感染人数超过15万触发邮件发送后，后续的每一次刷新一定也会满足超过15万人的条件，也就是说，每一次刷新都会发送邮件。\n\n\n\n尤其是学习了这篇：[如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483812&idx=1&sn=1d99a342ad280eac128845382886debb&chksm=ea674545dd10cc534548c85c923390ceddb1ea3688d8212f0c5697dbfe863183c52cd1e91128&scene=21#wechat_redirect)，假设设定了10分钟更新一次数据，邮件就会每10分钟发给我们一次，这很显然不是我们想要的。\n\n\n\n好人做到底，解决办法这里也一并告诉大家：\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOhFSYQRT6gCntGhzicVysOxeQmbvCEBDFeVxEhMUzicSoYR3uicKGGoUOXU0icjj1gmQ7NCjtMZNOpJNg/640?wx_fmt=jpeg)\n\n\n\n手动创建一个如下的excel文件：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOhFSYQRT6gCntGhzicVysOxe8mA34csb4dgSINianibibDCZ49U8PJ6M49W29h2ibgkzcZD0631GUCibCxQ/640?wx_fmt=png)\n\n\n\n修改发送邮件的条件，添加一条，pandas读取这个值，只有当这个值为0时才运行后面的内容；\n\n当发送邮件的条件满足时，0修改为1，并保存；\n\n这样，当满足一次条件后，条件就不再满足，后续也就不会再发送了：\n\nio=r\"C:\\Users\\学谦数据文化\\Desktop\\1.xlsx\"\ndf=pd.read_excel(io, sheet_name=0, header=0, names=None)\nif df.iat[0,0]==0:\n    for i in range(len(dataset)):\n        if dataset.iat[i,0]=='unitedstates':\n            if totalConfirmed>=150000:\n                df.iat[0,0]=1\n                df.to_excel(r'C:\\Users\\学谦数据文化\\Desktop\\1.xlsx', sheet_name='Sheet1')\n                #以下添加发送邮件的代码\n            \n\n\n\n完美解决！详细得不能再详细了。\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOhFSYQRT6gCntGhzicVysOxel0sQYebVU8KQ07D5BeMvk9TjjZsTbrc8vVd32NjyvRXGbxQgqDUZQg/640?wx_fmt=gif)\n\n*（对于最后这个问题，如果大家有更好的解决方案，欢迎在留言区留言或公众号内回复）*\n\n\n\n\n\n终于，我们通过5篇文章大体讲完了在Power BI中使用Python的几个场景：\n\n1、导入数据；\n\n2、清洗数据；\n\n3、数据可视化；\n\n4、PQ数据导出；\n\n5、数据预警与邮件自动发送。\n\n\n\n当然，我们需要注意的一点是，这些功能的使用其实很多时候并不是最合理的，也并不是鼓励大家一定要去这样操作，只是提供了一些新的思路。\n\n\n\n比如我们一般情况下都是Python爬取数据直接导入MySQL，然后再用pq导入Power BI中建模处理。但是在一些建造时间比较久了的模型中，原本就用pq爬取的数据并进行过大量处理，如果再转移到python，恐怕还得重新编写一遍代码，那么用本系列文章中的操作就会尽可能少地改动原来的代码，并节省不少时间。\n\n\n\n所以大家一定要择优选取使用。\n\n\n\n这几篇文章细节的点特别多，大家一定要自己动手操作几遍，有问题及时沟通，后续我会逐步安排相关的视频讲解，大家请注意关注本号，跟上队伍。\n\n\n\n------\n\n\n\n以下仍然是下期预告环节：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgNZYnvgSl4jznLias6MeovTCazgvzJicZHIfZd0JmsLw5uUTq2YNObW0BoLFbleLmFGcJZn6MIIYpA/640?wx_fmt=gif)\n\n\n\n不少公司的老板不习惯看报表，而是喜欢直接在群里看图片，尤其是每天定时几个时间点，看看完成率，看看PV、UV等，那么我们能不能设置一下，Power BI做好的报表，定时发送到钉钉群中呢？\n\n\n\n下一篇我们将继续介绍将Power BI和Python结合使用的第三种场景：\n\n\n\n**Python定时自动将Power BI报告截图发送钉钉群。**\n\n\n\n关于使用Python操控Power BI的网页端刷新，请阅读以下两篇文章：\n\n[如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483812&idx=1&sn=1d99a342ad280eac128845382886debb&chksm=ea674545dd10cc534548c85c923390ceddb1ea3688d8212f0c5697dbfe863183c52cd1e91128&scene=21#wechat_redirect)\n\n[如果雇一个人7d×24h每10秒刷新一次Power BI，我需要每月支付他多少钱？【2】](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483832&idx=1&sn=57165f8f1e91e463a26589e4652acc88&chksm=ea674559dd10cc4f57a04e48cf7696e54ae150e3a8df155489bdfc8ed69a67350d585f92848f&scene=21#wechat_redirect)\n\n\n\n![image-20200513174014244](https://tva1.sinaimg.cn/large/007S8ZIlly1geqympfu2gj31bi0q6kjm.jpg)\n\n------\n\n\n\n感谢您对【学谦数据运营】的关注、支持与厚爱，如果本文对您有用，请不要吝惜您的点赞、转发和点亮在看，有任何问题欢迎大家在留言区询问，谢谢。\n\n"
  },
  {
    "path": "_posts/2020-03-31-Power BI数据回写SQL Server（1）没有中间商赚差价.md",
    "content": "Power BI数据回写SQL Server（1）没有中间商赚差价\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacduvV5NuS96W85vl9qE1pAWI7SiagNerELIeMNNtarI7icTSaLbRP4l9Q/640?wx_fmt=jpeg)\n\n\n\n我们在[【重磅来袭】在Power BI 中使用Python（4）——PQ数据导出&写回SQL](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483996&idx=1&sn=65fbc72a39ba9168ef611110b3a16e54&chksm=ea6746bddd10cfab9ea97b3e0c418bc5f7c195ad5d7f75edfc11fd41242cbd87cf8ff6f37103&scene=21#wechat_redirect) 讲过如何在Power BI中调用Python实现powerquery获取和处理的数据回写到MySQL中。\n\n\n\n有不少朋友提问，能否回写到SQL SERVER中呢？\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgcmPd6SBjrlvB2EatBRdibj7pzCkMKwV27K2pR5cNSNjwtvH6WfAY2Z7R9hJibfa1mZFOMNWCHLIibA/640?wx_fmt=png)\n\n\n\n答案是肯定的。有两个大的解决方案：\n\n\n\n第一个，由于本质上我们调用的是Python脚本，所以回写入哪个数据库由Python来决定。写入MySQL的库是pymysql，而如果要写入SQL SERVER我们需要更换一个库：\n\npip install pymssql\n\n\n\n从名字上我们也能看出，这两个库的作者是同一个人，因此用法几乎完全一致。只不过在对待表名是中文时处理方式不太一样，MySQL需要在表名上加“`表名`”符号，SQL SERVER则不需要。\n\n\n\n点击：转换-运行Python脚本，编辑代码，运行。\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacViasO7NR5icg9TMfhGQbXo2rI3h1JRXOUsxOp7L89ZIFatIXbGiakCypQ/640?wx_fmt=gif)\n\n\n\n可以看到在运行Python脚本前，SQL数据库共378条数据，运行后是578条，增加了200条，这说明前几天只有189个国家和地区的数据，而今天更新有200个国家和地区的数据，这也直接说明病毒还在继续向更多国家蔓延，防控形势不容乐观。\n\n\n\n*获取完整源代码，请关注【学谦数据运营】，回复关键字“powerbi-python-sqlserver”\n*\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacic6463kveFzUWFRicV4DiarGpJVLtCjqTmxABJmhNIQw5jhbtYXDPzWhA/640?wx_fmt=png)\n\n\n\n\n\n第二个办法，其实更简单一些，而且直接跳过了Python，因为Power BI和SQL Server都是微软家的拳头产品，自家人，肯定办事方便些。\n\n\n\n我们先从SQL Server导入一张表到powerquery中：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacjDqN1Sk5dTJjpicSMNwpvKrdW6yXd57cMZMpfWXuOJYhrgYZib0CuoMg/640?wx_fmt=gif)\n\n\n\n点开高级编辑器：\n\n`let    \n    源 = Sql.Database(\"DESKTOP-NLIOB2L\\MSSQLSERVER1\", \"test1\"),    \n    dbo_Sheet1 = 源{[Schema=\"dbo\",Item=\"Sheet1\"]}[Data]`\n`in    dbo_Sheet1`\n\n\n\n将以上代码适当进行修改：\n\n`let    \n    源 = Sql.Database(\"DESKTOP-NLIOB2L\\MSSQLSERVER1\", \"test1\",[Query=\"select * from Sheet1\"])`\n`in    源`\n\n\n\n点运行：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacburAhDf9LROVgbEQXAkRNa1IhMZs3865Apzegia669zUKUHiagHp0kvA/640?wx_fmt=png)\n\n\n\n两种查询方式得到的结果完全一致。\n\n\n\n但是修改后的代码意义却变了：\n\n\n\n`[Query=\"select * from Sheet1\"]`\n\n\n\n这实现了在PowerQuery中直接输入SQL Server代码并运行：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacoIKzcF7gCicQvNpwKRYzzWbKRY9hUu0YB1ux1oEOUZy7jU3aoVTYYiaA/640?wx_fmt=png)\n\n\n\n这就代表着我们可以通过编写SQL语句向SQL Server插入数据了：\n\n`let    \n    Source = Excel.CurrentWorkbook(){[Name=\"表1\"]}[Content],    \n    ChangedType = Table.TransformColumnTypes(Source,\\{\\{\"KeyValue\", type text}, {\"NumberValue\", Int64.Type}, {\"DateValue\", type date}}),    \n    insert=Sql.Database(\"DESKTOP-NLIOB2L\\MSSQLSERVER1\", \"test1\",[Query=\"INSERT INTO Sheet1(KeyValue,NumberValue,DateValue)VALUES('A',3,'2019/1/1')\"])`\n`in    insert`\n\n\n\n看一下运行过程：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacN32FonLpSBVdsEkUHWowiawSiaR1WZ4dsQxCusRf7L34Gcm4eWAAQ2IA/640?wx_fmt=gif)\n\n\n\n可以看到原表中只有2017年的数据，运行后增加了5行2019/1/1的数据，查询一次却增加多行的原因我们在[【重磅来袭】在Power BI 中使用Python（4）——PQ数据导出&写回SQL](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483996&idx=1&sn=65fbc72a39ba9168ef611110b3a16e54&chksm=ea6746bddd10cfab9ea97b3e0c418bc5f7c195ad5d7f75edfc11fd41242cbd87cf8ff6f37103&scene=21#wechat_redirect)中也说过，尚未明确知晓什么原理，只能通过其他办法来处理，稍后再说。\n\n\n\n当然我们也可以同时插入多行数据：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacyPk74CffOBFHedEJ4fCL5gn05xV0WSecWf9R1hbmYqkUvGytynic2Qw/640?wx_fmt=png)\n\n\n\n结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacg5FPG3qBGhobFrsVdhYHpjhv29sds4sap5G9L3tsxicic8cxzLG8Gsibw/640?wx_fmt=png)\n\n\n\n但是这样我们只能实现自己手动填写数据写入SQL语句去运行，而无法将PQ查询的结果写入SQL。\n\n\n\n所以还得想别的办法。\n\n\n\n我们再来试试Value.NativeQuery方法，是将一条record记录数据直接插入数据库中：\n\n`Value.N`ativeQuery`\n        `(`\n        Sql.Database(\"DESKTOP-NLIOB2L\\MSSQLSERVER1\", \"test1\"),            \n        \"INSERT INTO Sheet1 VALUES(@KeyValue,@NumberValue,@DateValue)\",            \n        [KeyValue=\"NativeQuery\",NumberValue=3,DateValue=\"2019/1/1\"]        \n        )`\n\n运行结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacpq4srUgbP87rP0AhEvibfibal1iagj7SJJbZsgR5mx6ojHUeMkic7iccTrQ/640?wx_fmt=png)\n\n测试没有问题。\n\n\n\n那么重要的就来了：\n\n\n\n如果我们能够将PQ返回的表按行转换为一条条的record记录，再逐条导入SQL Server，那么我们的需求就得到了解决。\n\n\n\n第一步：使用Table.ToRecords函数将table转为record list：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvactq4wmsgsDqf0QVPuAibZ7AWiay9EbmeibEPsBc24sqH4SibuVdydLG2SeQ/640?wx_fmt=gif)\n\n\n\n第二步：我们再做一个循环，逐行读取这些records，并用Value.NativeQuery函数套在这些records上：\n\n`insert=List.Transform(records,(x)=>Value.NativeQuery(            \n            Sql.Database(\"DESKTOP-NLIOB2L\\MSSQLSERVER1\", \"test1\"),            \n            \"INSERT INTO Sheet1 VALUES(@KeyValue,@NumberValue,@DateValue)\",            \n            x        \n            ))`\n\n\n\n就得到结果了：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvace5mhib0ElicG1kLeOQfFTicZZeoXRaqNqLc8IZOzugowbDYfia8FskaPAA/640?wx_fmt=gif)\n\n\n\n还是那句感叹：\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOhFSYQRT6gCntGhzicVysOxen5x5xnzO5Zib2piclRHX7aoYrSZBkHsLVtSsvaOp1bgqMbvUlibP51XSQ/640?wx_fmt=jpeg)\n\n\n\n只不过，日期格式跟之前的并不太一致：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvac7yGJ66iaWcEC3hfgcwscDxKMAZf8HJ2Zs6HibaRJjtpt52cL4dOokTnw/640?wx_fmt=png)\n\n\n\n好在这并不是什么大问题，在SQL中设置一下datevalue字段的格式为date就可以搞定：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacjneLp8f1GibibbILN5WMcaib26KNdN79TsibFiaNzS0Sg6M7DdgQdf0m34Q/640?wx_fmt=png)\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOgfUZfjnTP7I2MY4D4f0G78t3M7lpbYGCM81Z4GORa6QoGvrroWjE8vqdVQictms7hwlibicG6DxicYQQ/640?wx_fmt=gif)\n\n\n\n至于刷新时重复导入或者每日刷新多次的问题，大家结合上一篇文章自己就可以解决，无非就是用DELETE函数，这里就不再赘述了。\n\n\n\n说到这里，我们再回过头来探讨一下Power BI和MySQL有没有可能也跳过Python这个“中间商”直接交易呢？\n\n\n\n看图：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacyrdfAbBHicl6tEUEapSvgx5Cee5ttSLVO8MRJPPFycCNJfvzwiciaMDdQ/640?wx_fmt=png)\n\n\n\n你说呢？\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacqqh0HicqdeX8NsXiaibvhmrKscg6sQNd6tGicic9K6v63h7KQogcBiab9Zicw/640?wx_fmt=jpeg)\n\n\n\n\n\n\n\n------\n\n\n\n以下，后续文章预告：\n\n\n\n今天我们讲的是PQ生成record列表，再逐个导入SQL中，那有没有办法将PQ中的table作为一个整体导入SQL中呢？\n\n\n\nPowerQuery还为我们提供了其他方式，比如调用存储过程。\n\n\n\n由于存储过程是SQL语言中很重要的一个内容，我们将用一整篇文章来详细说明，敬请期待。\n\n"
  },
  {
    "path": "_posts/2020-04-01-Power BI数据回写SQL Server（2）——存储过程一步到位.md",
    "content": "Power BI数据回写SQL Server（2）——存储过程一步到位\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacduvV5NuS96W85vl9qE1pAWI7SiagNerELIeMNNtarI7icTSaLbRP4l9Q/640?wx_fmt=jpeg)\n\n在上一讲：\n\n[Power BI数据回写SQL Server（1）没有中间商赚差价](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247484034&idx=1&sn=d2dcffec82178d3f88c8812cca68006c&chksm=ea674663dd10cf7556a00cfe8e8d150eb426c08de59605d956d97bdd7e5acbeb5fb0335e3052&scene=21#wechat_redirect) 中，\n\n\n\n我们讲过，利用循环的方式将PQ中得到的table表逐行导入SQL Server中，有的朋友怀疑这种方式会不会造成数据量较大时运行慢、能耗大的问题，这种顾虑理论上是恰当的，所以今天再介绍一种能够直接一次性导入SQL的办法。\n\n\n\n熟悉SQL的同学可能已经想到了——“存储过程”。我们可以通过创建一个存储过程来读取PQ生成的文件，然后解析到数据库中。\n\n\n\n用过这两种语言的朋友应该知道，PQ可以将查询结果的table转化为XML二进制文件或者JSON格式，而SQL恰好也能支持这两种文件格式的输入，这就好办了。\n\n\n\n*略微扫一下盲：*\n\n*JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于JavaScript的一个子集；*\n\n*XML（Extensible Markup Language）即可扩展标记语言，Xml是Internet环境中跨平台的，依赖于内容的技术，是当前处理结构化文档信息的有力工具。*\n\n*两者的共同优点是都是文本表示的数据格式，可以跨平台、跨系统交换数据。*\n\n\n\n一、XML篇：\n\n\n\n首先我们写一个带xml文件参数的存储过程：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs3189hs27oN0ic0ibLia7sChicEfTYeicNt6cUjJDB4MQS8xe2icL7yLOuudz6hqg/640?wx_fmt=png)\n\n\n\n这样我们就可以通过在SQL Server中直接调用这个函数来达到我们预先设定的插入数据的过程。\n\n\n\n我们看一下XMLbinary的格式：\n\n<table>\n    <row>\n        <KeyValue>学谦数据运营</KeyValue>\n        <NumberValue>35592</NumberValue>\n        <DateValue>2020/3/31</DateValue>\n    </row>\n</table>\n\n\n\n第二步，要将PQ返回的table转为以上的xml格式，我们需要在数据表中添加一列名为binary的自定义列，输入：\n\n=Text.Format\n  (                                        \n    \"<row><KeyValue>#[KeyValue]</KeyValue><NumberValue>#[NumberValue]</NumberValue><DateValue>#[DateValue]</DateValue></row> \",                                        \n    [KeyValue=[KeyValue], NumberValue=[NumberValue], DateValue=[DateValue]]\n  )\n\n\n\n运行：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacJKBr6fT8aQJO1o1A0vXmbtBt4bk7Prqr6J1fvuV0AaWh7MqJia8vh4g/640?wx_fmt=gif)\n\n\n\n这样我们就得到了新的一列：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacOF6xcib7fLMVa6ucCKibKeCQutuDTwDBTCeTeQHfgpxgdKI3M2ttKtjg/640?wx_fmt=png)\n\n\n\n接着，我们只用这一列，将这一列文本前后分别加上一个“table”然后用Text.ToBinary()转为XMLbinary文件：\n\nXmlBinary = Text.ToBinary(\"<table>\" & Text.Combine(AddedCustom[binary]) & \"</table>\")\n\n\n\n运行后我们就得到了一个XML二进制文件：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvacLEb7mbF884wL7EAwuvkgjrmLCz8ial55WqVKfejRCZQck2pQMa0cMeA/640?wx_fmt=gif)\n\n\n\n最后，我们要操作的就是将这个文件作为参数传递给SQL Server的存储过程，简单的一行代码：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs3189yIekU6PQJeIdmCXQ4EqMOdVMKfA5nZRV2Yf2ibsY4GzRlnJJicWov29g/640?wx_fmt=png)\n\n\n\n运行一下看看效果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaGZViakwNlseG9GNLIs31899LPibBicicxiaFym1U0KhQJVvvpBdGticGWXD08b0RW3EicCC1MibY6QK8hoA/640?wx_fmt=gif)\n\n\n\n原表中数据为0，刷新一次后插入20行数据，多次刷新后，数据每次增加20行。\n\n\n\nWOW，你们应该猜到我要说什么了：\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n![img](https://mmbiz.qpic.cn/mmbiz_jpg/OyXiackVTfOhFSYQRT6gCntGhzicVysOxen5x5xnzO5Zib2piclRHX7aoYrSZBkHsLVtSsvaOp1bgqMbvUlibP51XSQ/640?wx_fmt=jpeg)\n\n\n\n\n\n\n\n二、JSON篇\n\n\n\n第一步，在SQL Server中创建一个存储过程，调用json格式的文本为参数；\n\n\n\n第二步，powerquery生成JSON格式其实更加简单，使用Json.FromValue()，直接将table转为JSON文件：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaYFMzgSvXEHcsLicyWQtvac5J40WLAarBmLPROR3ViaA6RHVXhteyu0Fl0SHxJlEeeZ059qNJUHRpA/640?wx_fmt=gif)\n\n\n\n第三步，由于SQL读取的是字符串格式的JSON数据，所以需要使用Text.FromBinary()来返回字符串结果：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs3189VCDpVqEoUfTQngGSLKQM9zZdVkSHUMfKeYshAz5iaYqpEocB7CaOHQQ/640?wx_fmt=png)\n\n\n\n最后依然是向存储过程传递参数，只不过这次传递的是text格式JSON数据。\n\n\n\n好了，我们来看一下效果，舞动起来：\n\n![img](https://mmbiz.qpic.cn/mmbiz_gif/OyXiackVTfOiaGZViakwNlseG9GNLIs31896Zx0kGzjLFj0Nwldnrdpd8rGKddTWxU5tR6vtat6mTytPYQetPen3g/640?wx_fmt=gif)\n\n\n\n我们需要注意到，Text.FromBinary()获得的JSON字符串中文显示了Unicode编码字符，但是导入SQL中显示是中文没问题的：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs31890M6n588apKf7TYt7LnDKEaHImCgibaXOcBd5qFVxWkm8RibNgP7T95YA/640?wx_fmt=png)\n\n\n\n*这里留给大家一个问题，如果我就想在powerquery中显示中文，应该怎么办呢？欢迎大家在留言区交流分享。\n*\n\n\n\n\n\n好了，关于如何Power BI如何向SQL回写数据，我们用了三篇文章来讲解。前两篇分别是：\n\n\n\n[【重磅来袭】在Power BI 中使用Python（4）——PQ数据导出&写回SQL](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247483996&idx=1&sn=65fbc72a39ba9168ef611110b3a16e54&chksm=ea6746bddd10cfab9ea97b3e0c418bc5f7c195ad5d7f75edfc11fd41242cbd87cf8ff6f37103&scene=21#wechat_redirect)\n\n\n\n[Power BI数据回写SQL Server（1）没有中间商赚差价](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247484034&idx=1&sn=d2dcffec82178d3f88c8812cca68006c&chksm=ea674663dd10cf7556a00cfe8e8d150eb426c08de59605d956d97bdd7e5acbeb5fb0335e3052&scene=21#wechat_redirect)\n\n\n\n对这几篇文章做一个小总结：\n\n\n\nPower BI (PowerQuery)向SQL回写数据本身是一个应用场景并不多的技巧，没想到发了第一篇文章后很多朋友反馈说正是目前能用到的：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs3189rOwkG7MO2J0K5BtIQnAia6V91nDDhDUOgUaicxSKMVnh0pW9IRJPIMRQ/640?wx_fmt=png)\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs3189xicb10oVpcib6k2xTKDicDTRCInUg5n5k2ibF1Qx1hC47hytngQGqLLrMg/640?wx_fmt=png)\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOiaGZViakwNlseG9GNLIs318976fFw5qwEc52M0icSoTgrcRNKB29ooMDZGFvbMpWSLg5YqPz8Pwicwlw/640?wx_fmt=png)\n\n\n\n所以才有了后面的这两篇文章。\n\n\n\n总结起来，方法有这么几个：\n\n1、借助Python的相关库，在PQ中调用，以达到回写SQL的目的；\n\n2、在PQ中循环按行导入SQL；\n\n3、在SQL中创建存储过程，然后在PQ中调用存储过程，JSON或XML文件作为参数\n\n\n\n同时，总结了几位朋友的案例，发现应用场景主要集中在这么两个方面：\n\n①pq爬取的数据只是状态数据，转瞬即逝，无法变化记录；\n\n②解决不同数据库之间的壁垒，比如要定期将数据从某个数据库中备份复制到另一个。\n\n\n\n所以，如果你在日常工作学习中遇到了以上的应用场景，那么这三篇文章恰好能够帮助你。\n\n\n\n不过，我在测试时用的都是几十行的小数据，这几种方法并没有感觉到任何区别，所以对于巨量数据是否会有差异并没有经过实际的测试，欢迎有兴趣的朋友对比一下这几种方式，比较一下优缺点，欢迎反馈。\n\n\n\n\n\n------\n\n\n\n感谢您对【学谦数据运营】的关注、支持与厚爱，如果本文对您有用，请不要吝惜您的点赞、转发和点亮在看，有任何问题欢迎大家在留言区询问或者直接加我个人微信，谢谢。\n\n"
  },
  {
    "path": "_posts/2020-04-04-同一台电脑管理多家企业Power BI报表的自动更新.md",
    "content": "# 2021.4.8更新：\n\n其实随着后续做的项目越来越多以及个人测试的需求，在同一台电脑上管理两三个账号的报表已经明显不够用了。\n\n因此几乎所有的数据都会存放在云端SharePoint或者云数据库中。免除了本地网关的烦扰，刷新起来会更加方便，尤其是配合无限刷新来使用更是得心应手。\n\n\n\n\n\n# 以下为原文\n\n同一台电脑管理多家企业Power BI报表的自动更新\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFw0qibFRwBOPiaHD5C9GvicJYgE2WRFyDdibDq1TvtusNlX37r1oBPwE5lAw/640?wx_fmt=png)\n\n手里头拿着本公司的Power BI管理员账号，原本呢，一台电脑一个人一天处理一家公司的一亿数据是比较得劲的，尤其是有了Python自动任意刷新Power BI更是美滋滋。\n\n\n\n可是最近手里头又捏了一家公司的管理员账号（详情见次条），本想着这一台电脑可以同时自动刷新两家公司，但是在登录网关时发现，登录了B公司的账号，A公司的网关自动就断开了，登录了A，B就掉了。\n\n\n\n也就是说，同一时间，只能有一家公司自动刷新。这很显然不符合我们一贯的强行绕开微软限制的习惯。\n\n\n\n仔细阅读了官方文档，发现，微软也还算仁慈：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwmIJozXKKrGf7Al4LyZkl5SnqjCcfVWd6Y0fOzPm8YZmweSib1p4VCCg/640?wx_fmt=png)\n\n\n\n原文文档：\n\nhttps://docs.microsoft.com/zh-cn/data-integration/gateway/service-gateway-install\n\n\n\n也就是说我们可以同时安装两个网关，而两个网关分别管理不同的企业。而文档也说的很清楚，同一个网关同一个模式只能登陆一个账号。\n\n\n\n所以我们可以在一台电脑上同时用这两个网关了：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwNL73Y57FUvEjFhPbGf601vLat2kYEd5kjkvibnBzRMxAfx23fjZDiaGw/640?wx_fmt=png)\n\n以上是标准模式，以下个人模式：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwFF2AMBfKicvIibSdxfPCcwwTOTdV0s9h5CicDWvnR2lmO6htARHp0JXqQ/640?wx_fmt=png)\n\n\n\n这样我们就可以同时在同一台电脑上管理两家公司的自动刷新了。\n\n\n\n但是，如果两家公司的数据源没有任何交集，那么两个刷新可以任意设定时间，不受影响。而如果两个账号的数据源是同一个而且刷新时间还存在交叉，那么有较大概率其中一个刷新时会发生错误：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwv0Xicx9icOZzc1sFTxDD42DUkx7z63MeviamwIYEQyGuTannZQsTS3hJQ/640?wx_fmt=png)\n\n或者更新时间远远超过正常值：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwtGPmdC0uLzoiblicH0gsvcnibdoag6Y25QeaB4W3pgxQiaHbkehc6tndoA/640?wx_fmt=png)\n\n\n\n\n\n而一旦刷新时间没有交集，一切就会恢复正常：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwroZk0vMPJl0Kg9kfZgvrib4LI4X16HLtn5c8ibfa70ogu2keDCTgYngQ/640?wx_fmt=png)\n\n另一个账号的刷新时间：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwEIH0JtVhh5lTahmibKtKdHIOpibWzqHVJA1pYez4T82icP7fBvNWk7DuQ/640?wx_fmt=png)\n\n\n\n这样，我们就实现了同时管理两个公司的目的。\n\n\n\n或许这也是为什么微软不允许同一类型网关登录两个账号的原因。\n\n\n\n\n\n其实我这两个账号的报表内容完全一致，数据源也一模一样，因为本地的模型都是一个。A账号是公司购买的世纪互联的账号，B账号是开发版，模型是同一个，但是使用的人不同。至于为什么，这就是企业内部的怪事之一了。\n\n\n\n这里先挖个坑，说不定哪天我们就能像突破每天8次刷新的限制那样突破这个限制了，哈哈。\n\n\n\n另外，算是一个建议，也是自己踩过的坑，尽可能数据源都在数据库中，而不是散落在各个excel表中：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwI59icB1ndh9ciaf6NoXZEBDm8QxEwW3KtsjDBuicapJ6E2N5z98AGiafeQ/640?wx_fmt=png)\n\n\n\n每一个excel表都是一个单独的数据源，而无论你从数据库的多少个表中查询数据，数据源只有这一个数据库：\n\n![img](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOgpSv5WMPeVBJp3ruQFQbFwFzIgNoevz4D3NQpFIfFGMnpVfJZdbbFnwNNDqNWvJibyzl1PsibyNJ7g/640?wx_fmt=png)\n\n\n\n以上只是这一个模型数据源的一部分，说多了都是泪……\n\n\n\n\n\n------\n\n\n\n感谢您对【学谦数据运营】的关注、支持与厚爱，如果本文对您有用，请不要吝惜您的点赞、转发和点亮在看，有任何问题欢迎大家在留言区询问或者直接加我个人微信，谢谢。\n\n"
  },
  {
    "path": "_posts/2020-04-11-Python自动将Power BI页面发送钉钉群.md",
    "content": "前文说过，在很多个惬意的下午，我每每爽歪歪地喝着咖啡，看着Power BI每秒钟刷新一次，静静等待某个分公司完成本月绩效任务，自动调用Python在钉钉群中发送喜报：\n\n![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq52a63ugj30gu0kc3z6.jpg)\n\n\n\n紧接着再次调用Python将Power BI云端报告中的各分公司最新完成率数据和柱状图截图发在群里：\n\n![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq51qiky4j30gy0gw3zo.jpg)\n\n\n\n那么今天就来讲一讲如何使用Python自动将Power BI报表中的页面截图发送到钉钉群或企业微信群中。\n\n\n\n首先我们来拆解一下整个过程：\n\n首先需要用Python登录Power BI打开所要截图的页面，并截图保存到本地，是为第一步。\n\n如果要发送图片到钉钉群或企业微信群中，需要以markdown格式发送，图片需要为链接而不是文件，这是第三步。\n\n再来说中间的第二步，要实现本地图片到图片链接的转换，需要一个Python可调用的稳定图床，所以找到合适的图床很重要。\n\n\n\n明白了这三步，我们就可以开始干活了。\n\n一、登录Power BI并截图\n\n我们在无限刷新Power BI的第一篇文章中讲过，使用selenium的webdriver就能实现，截图可以用selenium配合PIL库实现。当然，前提是需要提前获取所要截图的报表页面。\n\n\n\n登录代码，马赛克区域替换为自己的用户名和密码：\n\n![image-20200411213507847](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq5lczrmnj30q8076n05.jpg)\n\n\n\n\n\n截图代码：\n\n![image-20200411213535014](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq5lugwp1j30t60ag42g.jpg)\n\n\n\n截图时首先截取了全部浏览器，然后用四个角的坐标获取报表范围。最后保存到本地图片文件。\n\n\n\n二、将本地文件上传云端并获取链接\n\n这里我们使用的是七牛云。注册一下，然后创建个自有空间，设置好后，参考下文这个链接设置好SDK。\n\nhttps://developer.qiniu.com/kodo/sdk/1242/python\n\n\n\n将文件路径和文件名作为参数传递给函数，获取链接：\n\n![image-20200411214024056](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq5qukr4dj30h406u76d.jpg)\n\n第二步结束。\n\n\n\n三、发送钉钉群\n\n1.在钉钉群中添加自定义机器人，并获取Webhook（注意Webhook不要泄露）：\n\n![image-20200411214924765](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq6089s06j30mm0fgac1.jpg)\n\n\n\n然后设置好markdown格式的消息，确定好要@的人即可：\n\n![image-20200411220321669](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq6es6b19j30jo0be41x.jpg)\n\n\n\n好了，我们来看以下成品。\n\n![image-20200411220254105](https://tva1.sinaimg.cn/large/007S8ZIlly1gdq6e9gen7j30mc0hoqad.jpg)\n\n\n\n还是很简单的对吧。\n\n\n\n获取源代码，请关注【学谦数据运营】，回复“开车”。"
  },
  {
    "path": "_posts/2022-04-05-Power BI Desktop 入门 - Power BI  Microsoft Docs.md",
    "content": "# Power BI Desktop 入门 - Power BI | Microsoft Docs\n\n其实接触Power BI这么多年，一直觉得官方给出的入门教程才是最好的。所以，入门课程不要想着去报什么班什么课程，好好看看这篇文章，吃透了，Power BI也就入门了。\n\n原文地址：\n\n[Power BI Desktop 入门 - Power BI | Microsoft Docs](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/desktop-getting-started)\n\n## 本文内容\n\n1. [Power BI Desktop 工作原理](about:blank#how-power-bi-desktop-works)\n2. [安装并运行 Power BI Desktop](about:blank#install-and-run-power-bi-desktop)\n3. [连接到数据](about:blank#connect-to-data)\n4. [调整数据](about:blank#shape-data)\n5. [合并数据](about:blank#combine-data)\n6. [生成报表](about:blank#build-reports)\n7. [共享工作](about:blank#share-your-work)\n8. [后续步骤](about:blank#next-steps)\n\n欢迎使用 Power BI Desktop 入门指南。 本教程介绍 Power BI Desktop 的工作原理和功能，并介绍如何生成可靠的数据模型和奇妙的报表来提升你的商业智能。\n\n要快速了解 Power BI Desktop 的工作原理及其使用方式，只需花几分钟浏览一下本指南中的屏幕。 要更深入地了解，可通读各个部分，执行相关步骤，创建自己的 Power BI Desktop 文件并将其发布到 [Power BI 服务](https://app.powerbi.com/)以与他人共享。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/hero-02.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/hero-02.png)\n\n还可观看 [Power BI Desktop 入门](https://www.youtube.com/watch?v=Qgam9M8I0xA)视频，并下载 [财务示例](https://go.microsoft.com/fwlink/?LinkID=521962) Excel 工作簿按视频进行操作。\n\n重要\n\n每月更新并发布 Power BI Desktop，在其中包含客户反馈和新增功能。 仅支持 Power BI Desktop 的最新版本；将要求联系 Power BI Desktop 支持的客户升级到最新版本。 可以从 [Windows 应用商店](https://aka.ms/pbidesktopstore)获取 Power BI Desktop 的最新版本，也可以在计算机上以单个可执行文件的形式[下载](https://www.microsoft.com/download/details.aspx?id=58494)并安装所有受支持的语言。\n\n## Power BI Desktop 工作原理\n\n使用 Power BI Desktop，可以：\n\n1. 连接到数据，包括多个数据源。\n2. 借助可生成见解深刻、令人信服数据模型的查询来调整数据。\n3. 使用数据模型创建可视化效果和报表。\n4. 共享报表文件以供他人使用、用作基础文件和共享。 可像任何其他文件一样共享 Power BI Desktop“.pbix” 文件，但最具吸引力的方法是将其上传到 [Power BI 服务](https://preview.powerbi.com/)。\n\nPower BI Desktop 集成了久经考验的 Microsoft 查询引擎、数据建模和可视化技术。 数据分析师和其他人员可以创建查询、数据连接、模型和报表集合，并轻松与他人共享。 通过组合 Power BI Desktop 和 Power BI 服务，数据世界的新见解将更易于建模、生成、共享和扩展。\n\nPower BI Desktop 会集中、简化并效率化设计与创建商业智能存储库和报表的程序，这些程序可能是散乱、不相关且棘手的。 准备好要试一试吗？ 现在就开始吧。\n\n备注\n\n对于必须保留在本地的数据和报表，Power BI 提供一个单独的专业版本，名为 [Power BI 报表服务器](https://docs.microsoft.com/zh-cn/power-bi/report-server/get-started)。 Power BI 报表服务器使用单独的专业版 Power BI Desktop，该版本名为用于 Power BI 报表服务器的 Power BI Desktop，并且仅适用于 Power BI 的报表服务器版本。 本文介绍标准版 Power BI Desktop。\n\n## 安装并运行 Power BI Desktop\n\n要下载 Power BI Desktop，请前往 [Power BI Desktop 下载页面](https://powerbi.microsoft.com/desktop)，然后选择 “免费下载”。 或者对于下载选项，选择[查看下载或语言选项](https://www.microsoft.com/download/details.aspx?id=58494)。\n\n还可从 Power BI 服务下载 Power BI Desktop。 在顶部菜单栏中选择 “下载” 图标，然后选择“Power BI Desktop” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_download.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_download.png)\n\n在 Microsoft Store 页面上，选择 “获取”，然后按照提示在计算机上安装 Power BI Desktop。 从 Windows“开始” 菜单或从 Windows 任务栏中的图标启动 Power BI Desktop。\n\nPower BI Desktop 首次启动时，会显示 “欢迎” 屏幕。\n\n在 “欢迎” 屏幕中，可获取数据，查看最近使用的源，打开最近使用的报表，打开其他报表，或选择其他链接 。 选择关闭图标可关闭 “欢迎” 屏幕。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_startsplashscreen.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_startsplashscreen.png)\n\nPower BI Desktop 左侧为三个 Power BI Desktop 视图的图标，从上到下为：“报表”、“数据” 和 “模型”。 左侧的黄色栏指示当前视图，可通过选择任一图标来更改视图。\n\n如果使用的是键盘导航，请按 Ctrl + F6，将焦点移到窗口中的相应按钮部分。 若要详细了解辅助功能和 Power BI，请访问[辅助功能文章](https://docs.microsoft.com/zh-cn/power-bi/create-reports/desktop-accessibility-creating-tools)。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_viewtypes.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_viewtypes.png)\n\n“报表” 视图为默认视图。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_blankreport.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_blankreport.png)\n\nPower BI Desktop 还包括 “Power Query 编辑器”，它将在在单独的窗口中打开。 在“Power Query 编辑器” 中，可生成查询和转换数据，然后将经过细化的数据模型加载到 Power BI Desktop 以创建报表。\n\n## 连接到数据\n\n安装 Power BI Desktop 后，便可连接到不断扩展的数据世界。 要查看多种可用的数据源，请在 Power BI Desktop 的 “主页” 选项卡中选择 “获取数据”>“更多”，然后在“获取数据” 窗口中，滚动浏览 “所有” 数据源的列表 。 在本快速教程中，你将连接到几个不同的 “Web” 数据源。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/getdataweb.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/getdataweb.png)\n\n假设你是一家太阳镜零售商的数据分析师。 你希望帮助客户在四季阳光充足的州销售太阳镜。 Bankrate.com [最佳和最糟退休州](https://www.bankrate.com/retirement/best-and-worst-states-for-retirement/)页面上提供了关于本主题的有趣数据。\n\n在 Power BI Desktop 的 “主页” 选项卡中，选择 “获取数据”>“Web” 以连接到 Web 数据源 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_syw_2.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_syw_2.png)\n\n在 “从 Web” 对话框中，将地址 *[https://www.bankrate.com/retirement/best-and-worst-states-for-retirement/](https://www.bankrate.com/retirement/best-and-worst-states-for-retirement/)* 粘贴到 “URL” 字段，然后选择“确定”。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gettingstarted_8.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gettingstarted_8.png)\n\n如果出现提示，请在 “访问 Web 内容” 屏幕上，选择 “连接” 以使用匿名访问 。\n\nPower BI Desktop 的查询功能开始运行并与 Web 资源联系。 “导航器” 窗口返回它在网页上找到的内容，在本例中，它返回一个名为 “Ranking of best and worst states for retirement”（全美最佳与最差退休居住州排名）的 HTML 表和其他五个建议的表 。 你对 HTML 表感兴趣，因此选择它进行预览。\n\n此时，你可以选择 “加载” 来加载该表，也可以选择 “转换数据” 以在表中进行更改，然后再加载 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/datasources_fromnavigatordialog.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/datasources_fromnavigatordialog.png)\n\n如果选择 “转换数据”，Power Query 编辑器将会启动，并显示表的代表性视图。 “查询设置” 窗格在右侧，你也可以通过如下操作来显示它：在 Power Query 编辑器的 “视图” 选项卡上，选择“查询设置” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_editquery.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_editquery.png)\n\n有关连接到数据的详细信息，请参阅[连接到 Power BI Desktop 中的数据](https://docs.microsoft.com/zh-cn/power-bi/connect-data/desktop-connect-to-data)。\n\n## 调整数据\n\n现在你已经连接到数据源，你可以调整数据以符合需求。 要调整数据，请在加载和呈现数据时为 Power Query 编辑器提供调整数据的分步说明。 调整不会影响原始数据源，只会影响数据的这一特定视图。\n\n备注\n\n本指南中使用的表数据可能随时更改。 因此，你需要遵循的步骤可能有所不同，这就要求你在调整步骤或结果方面具有创造性，这也体现了学习的乐趣。\n\n调整可能意味着转换数据，例如，重命名列或表、删除行或列，或者更改数据类型。 Power Query 编辑器在 “查询设置” 窗格中的 “已应用步骤” 下依次捕获这些步骤 。 每当此查询连接到数据源时，都会执行这些步骤，这样，数据将始终以指定的方式进行调整。 每当你在 Power BI Desktop 中使用查询时，或任何人使用你的共享查询（例如在 Power BI 服务中）时，都会执行此过程。\n\n注意，“查询设置”中的 “已应用步骤” 已经包含了一些步骤 。 你可以选择每个步骤来查看其在 Power Query 编辑器中的效果。 一开始，你指定了一个 Web 源，然后在 “导航器” 窗口中预览了表。 在第三步中，更改了类型，Power BI 在导入时识别了整数数据，并自动将原始 Web“文本”数据类型更改为了“整数”。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_appliedsteps_changedtype.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_appliedsteps_changedtype.png)\n\n如果需要更改数据类型，请选择一列或多列进行更改。 按住 “Shift” 键可选择多个相邻的列，按住 “Ctrl” 键可选择非相邻列 。 右键单击列标题，选择 “更改类型”，然后在菜单中选择新数据类型；或者在“主页” 选项卡的 “转换” 组中，下拉 “数据类型” 旁边的列表，然后选择新数据类型 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_changedatatype.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_changedatatype.png)\n\n备注\n\nPower BI Desktop 中的 Power Query 编辑器使用功能区或右键单击菜单来执行可用任务。 大部分可在 “主页” 或功能区的 “转换” 选项卡上选择的任务，也可以通过右键单击一个项目并在出现的菜单中进行选择来实现 。\n\n现在，你可以对数据应用自己的更改和转换，并在 “已应用步骤” 中查看。\n\n例如，对于太阳镜销售，你最想了解的是天气排名，因此，你决定按照 “天气” 列而不是 “整体排名” 来对表进行排序 。 下拉 “天气” 标头旁边的箭头，并选择 “升序排序” 。 数据现在按照天气排名排序，并且“已应用步骤” 中会出现 “已对行排序” 这一步骤 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine-changetype-b.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine-changetype-b.png)\n\n你不是很想把太阳镜卖给天气最糟糕的州，所以你决定将它们从表中删除。 从 “主页” 选项卡中，依次选择 “减少行”>“删除行”>“删除最后几行” 。 在“删除最后几行” 对话框中，输入“10”，然后选择“确定” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata3.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata3.png)\n\n将从表中删除最后 10 行天气最糟糕的州，并且 “已应用步骤” 中会出现 “已删除最后几行” 这一步骤 。\n\n你认为表中包含太多不必要的信息，决定删除 “可购性”、“犯罪”、“文化” 和“健康”列 。 选择要删除的各列的标头。 按住 “Shift” 键可选择多个相邻的列，按住 “Ctrl” 键可选择非相邻列 。\n\n然后，从 “主页” 选项卡的 “管理列” 组中，选择 “删除列” 。 你还可右键单击其中一个选定的列标头，然后在菜单中选择“删除列”。 此操作将删除选定的列，并且“已应用步骤” 中会出现“已删除列” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata3a.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata3a.png)\n\n再想想，“可购性”可能与太阳镜的销售有关。 你希望恢复该列。 通过选择步骤旁边的 “X” 删除图标，可以轻松撤消 “已应用步骤” 窗格中的最后一步 。 现在重新执行步骤，仅选择要删除的列。 为了获得更大的灵活性，可以分步删除每一列。\n\n你可以右键单击 “已应用步骤” 窗格中的任何步骤，然后选择删除它、对其进行重命名、在序列中上移或下移，或在其后添加或删除步骤。 对于中间步骤，如果更改可能会影响后续步骤并中断查询，Power BI Desktop 将向你发出警告。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_install.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_install.png)\n\n例如，如果不再希望根据 “天气” 对表进行排序，可以尝试删除 “已对行排序” 步骤 。 Power BI Desktop 会警告你删除此步骤可能会导致查询中断。 按天气排序后，你删除了最后 10 行，因此，如果删除排序，将删除不同的行。 如果选择 “已对行排序” 步骤，并尝试在此添加新的中间步骤，也会出现警告。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/deletestepwarning.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/deletestepwarning.png)\n\n最后，将表标题更改为关于太阳镜销售，而不是退休。 在 “查询设置” 窗格中的 “属性” 下，将旧的标题替换为“最佳太阳镜销售州” 。\n\n完成后的已调整数据查询如下所示：\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_querysettingsfinished.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_querysettingsfinished.png)\n\n有关调整数据数据的详细信息，请参阅[在 Power BI Desktop 中调整和合并数据](https://docs.microsoft.com/zh-cn/power-bi/connect-data/desktop-shape-and-combine-data)。\n\n## 合并数据\n\n有关各州的那份数据很有趣，而且适用于生成其他分析工作和查询。 但是有一个问题：大多数数据使用两个字母的州代码缩写，而不是完整名称。 要使用该数据，需要通过某种方式来将州名与其缩写关联。\n\n你很幸运。 另一个公共数据源可以做到这一点，但你需要对数据进行大量的调整，然后才能将数据合并到太阳镜表。\n\n要将州缩写数据导入 Power Query 编辑器，请在功能区 “主页” 选项卡上的 “新建查询” 组中，选择“新增源”>“Web” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gettingstartedsplash_resized.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gettingstartedsplash_resized.png)\n\n在 “从 Web” 对话框中，输入州缩写站点的 URL：*[https://en.wikipedia.org/wiki/List\\_of\\_U.S.\\_state\\_abbreviations](https://en.wikipedia.org/wiki/List_of_U.S._state_abbreviations)*。\n\n在 “导航器” 窗口中，选择 “美国各州、联邦地区、领地及其他区域的代码和缩写” 表，然后选择“确定” 。 该表将在 Power Query 编辑器中打开。\n\n删除 “区域的名称和状态”、“区域的名称和状态” 和“ANSI”之外的所有列 。 要仅保留这些列，请按住 “Ctrl” 并选择相应的列。 然后，右键单击其中一个列标头并选择 “删除其他列”，或者在“主页” 选项卡的 “管理列” 组中，选择“删除其他列” 。\n\n下拉 “区域 1 的名称和状态” 列标头旁边的箭头，并选择 “筛选器”>“等于” 。 在“筛选行” 对话框中，下拉 “等于” 旁边的 “输入或选择值” 字段，然后选择“州” 。 选择“确定”。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/filterrows.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/filterrows.png)\n\n删除了 “联邦地区” 和“岛”这样的额外值后，现在就有了一个包含 50 个州及其官方双字母缩写形式的列表 。 你可以重命名列以便更好理解，例如 “州名”、“状态” 和“缩写”，方法是右键单击列标头并选择“重命名” 。\n\n注意，所有这些步骤都记录在 “查询设置” 窗格中的 “已应用步骤” 下 。\n\n调整后的表现在如下所示：\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/statecodes.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/statecodes.png)\n\n在 “查询设置” 的“属性”字段中，将表重命名为“州代码” 。\n\n“州代码” 表调整完成后，你可以将这两个表合并为一个表。 由于你现在拥有的表是你向数据应用查询之后的结果，因此它们也称为 “查询”。 有两种主要方法可合并查询：合并和追加 。\n\n当你有一列或多列要添加到另一个查询时，你可合并这些查询。 当你有其他数据行要添加到现有查询时，你可追加查询。\n\n在本例中，你希望将 “州代码” 查询合并到 “最佳太阳镜销售州” 查询 。 要合并查询，请切换到 “最佳太阳镜销售州” 查询，方法是从 Power Query 编辑器左侧的 “查询” 窗格中选择它 。 然后在功能区 “主页” 选项卡中的 “合并” 组中，选择“合并查询” 。\n\n在 “合并” 窗口中，下拉字段，可从其他可用查询中选择 “州代码” 。 在每个表中选择要匹配的列，在本例中，为“最佳太阳镜销售州” 查询中的 “州” 和“州代码”查询中的“州名” 。\n\n如果出现 “隐私级别” 对话框，请选择“忽略此文件的隐私级别检查”，然后选择“保存” 。 选择“确定”。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_merge.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_merge.png)\n\n“最佳太阳镜销售州”表的右侧将出现一个名为 “州代码” 的新列 。 它包含与 “最佳太阳镜销售州” 查询合并的 “州代码” 查询。 合并后的表中的所有列都压缩到 “州代码” 列中。 你可以展开合并后的表并仅包含所需的列。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/mergedquery.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/mergedquery.png)\n\n要展开合并后的表，并选择要包含的列，请选择列标头中的 “展开” 图标。 在 “展开” 对话框中，仅选择 “缩写” 列 。 取消选中“使用原始列名作为前缀”，然后选择“确定” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_mergeexpand.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_mergeexpand.png)\n\n备注\n\n你可以尝试一下如何引入 “州代码” 表。 尝试一下，如果不喜欢结果，只需从 “查询设置” 窗格中的 “已应用步骤” 列表中删除该步骤即可 。 这就像是个自由重做的机会，你可以不限次数地任意执行，直到展开过程看起来是你要的方式为止。\n\n有关调整及合并数据步骤的更完整说明，请参阅[在 Power BI Desktop 调整和合并数据](https://docs.microsoft.com/zh-cn/power-bi/connect-data/desktop-shape-and-combine-data)。\n\n现在，你在单个查询表中合并了两个数据源，每个数据源都已根据需要进行调整。 此查询可以作为许多其他有趣数据连接的基础，例如各州的人口统计、财富水平或娱乐机会。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/mergedcolumn.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/mergedcolumn.png)\n\n到目前为止，你有足够的数据在 Power BI Desktop 内创建有趣的报表。 由于这是一个里程碑，请在 “Power Query 编辑器” 中应用更改，并将其加载到 Power BI Desktop 中，方法是：在功能区的 “主页” 选项卡中选择“关闭并应用”。 你还可以仅选择“应用”，以确保在 Power Query 工作时使查询在 Power Query 编辑器处于打开状态。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_closeandapply.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_closeandapply.png)\n\n将表加载到 Power BI Desktop 后，你可以对其进行更多更改，并重新加载模型以应用所做的任何更改。 要从 Power BI Desktop 重新打开 “Power Query 编辑器”，请在 Power BI Desktop 功能区的“主页” 选项卡上选择“转换数据”。\n\n## 生成报表\n\n在 Power BI Desktop“报表” 视图中，你可以生成可视化效果和报表。 “报表” 视图包含六个主要区域：\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_reportview.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_reportview.png)\n\n1. 功能区位于顶部，它显示与报表和可视化效果相关联的常见任务。\n2. 画布区域位于中间，可在其中创建和排列可视化效果。\n3. 页面选项卡区域位于底部，它用于选择或添加报表页。\n4. “筛选器” 窗格，可在其中筛选数据可视化效果。\n5. “可视化效果” 窗格，可在其中添加、更改或自定义可视化效果，并应用钻取。\n6. “字段”窗格，它显示查询中的可用字段。 你可以将这些字段拖放到画布、“筛选器”窗格或 “可视化效果” 窗格，以创建或修改可视化效果 。\n\n通过选择窗格顶部的箭头，可以展开和折叠 “筛选器”、“可视化效果” 和“字段”窗格 。 折叠窗格可在画布上提供更多空间来生成炫酷的视觉化效果。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_collapsepanes.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_collapsepanes.png)\n\n要创建简单的可视化效果，只需在 “字段” 列表中选择任意字段，或将字段从 “字段” 列表拖到画布上。 例如，将 “州” 字段从 “最佳太阳镜销售州” 拖到画布上，看看会发生什么 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_reportfirstdrag.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_reportfirstdrag.png)\n\n看一下！ Power BI Desktop 识别到 “州” 字段包含地理位置数据并自动创建了基于地图的可视化效果。 可视化效果在数据模型中显示了 40 个州的数据点。\n\n“可视化效果” 窗格显示有关可视化效果的信息，并允许你对其进行修改。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_visualizationtypes.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_visualizationtypes.png)\n\n1. 图标显示创建的可视化效果的类型。 你可以通过选择不同的图标来更改所选可视化效果的类型，或通过选择未选定现有可视化效果的图标来创建新的可视化效果。\n2. 可利用 “可视化效果” 窗格中的 “字段” 选项将数据字段拖动到 “图例” 和窗格中的其他字段 。\n3. 可利用 “格式” 选项将格式设置和其他控件应用到可视化效果。\n\n“字段”和 “格式” 区域中的可用选项取决于你拥有的可视化效果和数据类型 。\n\n你希望地图可视化效果仅显示天气最佳的前 10 个州。 要仅显示前 10 个州，请在 “筛选器” 窗格中，将鼠标悬停在 “州为(所有)” 上并展开出现的箭头 。 在 “筛选器类型” 下，下拉并选择 “前 N 个” 。在“显示项目” 下，选择“最后”，因为你希望显示数字级别最低的项目，并在下一个字段中输入“10” 。\n\n在 “字段” 窗格中，将 “天气” 字段拖动到 “按值” 字段中，然后选择“应用筛选器” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share5.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share5.png)\n\n现在，地图可视化效果中仅显示天气最佳的前 10 个州。\n\n通过以下方式可重新设置可视化效果标题：在 “可视化效果” 窗格中选择 “格式” 图标，选择 “标题”，并在“标题文本” 下键入“天气最佳的前 10 个州” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_report1.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_report1.png)\n\n要添加显示天气最佳的前 10 个州的名称及其从 1 到 10 的排名，请选择画布的空白区域，然后在 “可视化效果” 窗格中选择 “柱状图” 图标 。 在 “字段” 窗格中，选择 “州” 和“天气” 。 柱状图显示了查询中的 40 个州，从最高到最低的数字级别或者从最糟到最佳天气进行排名。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share7.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share7.png)\n\n要切换排序顺序以使数字 1 第一个出现，请在可视化效果的右上角选择 “更多选项” 省略号，然后在菜单中选择“升序排序” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_mergequeries.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_mergequeries.png)\n\n要将表限制为前 10 个州，请应用与地图可视化效果相同的后 10 筛选器。\n\n以与地图可视化效果相同的方式重新命名可视化效果。 同样在 “可视化效果” 窗格的 “格式” 部分，将 “Y 轴”>“轴标题” 从“天气”更改为 “天气排名”，使其更易于理解 。 然后，将“Y 轴” 选择器切换到 “禁用” 。 将缩放滑块切换到“启用”，并将“数据标签” 切换到“启用” 。 最后，沿 Y 轴调整缩放滑块，直到堆积柱形填满图表。\n\n现在，天气最佳的前 10 个州按排名顺序显示，并显示其数字排名。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_changetype.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/shapecombine_changetype.png)\n\n你可以为 “可购性” 和“总体排名”字段提供类似或其他可视化效果，或将多个字段合并为一个可视化效果 。 你可以创建各种相关报表和可视化效果。 这些 “表” 和“折线和簇状柱形图”可视化效果显示天气最佳的前 10 个州以及其可购性和总体排名 ：\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_report2costofliving.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/designer_gsg_report2costofliving.png)\n\n你可以在不同的报表页上显示不同的可视化效果。 要添加新页面，请选择页面栏上现有页面旁边的 **+** 符号，或在功能区的 “主页” 选项卡中选择“插入”>“新页面”。 要重命名某个页面，请在页面栏中双击该页面的名称，或右键单击它并选择“重命名页面”，然后键入新名称。 要切换到报表的其他页面，请从页面栏中选择该页面。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pages.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pages.png)\n\n你可以通过 “主页” 选项卡的 “插入” 组将文本框、图像和按钮添加到报表页 。要设置可视化效果的格式设置选项，请选择可视化效果，然后在 “可视化效果” 窗格中选择 “格式” 图标 。 要配置页面大小、背景和其他页面信息，请选择未选定可视化效果的 “格式” 图标。\n\n创建好页面和可视化效果后，选择 “文件”>“保存”，可保存报表 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/finished-report.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/finished-report.png)\n\n有关报表的详细信息，请参阅 [Power BI Desktop 中的报表视图](https://docs.microsoft.com/zh-cn/power-bi/create-reports/desktop-report-view)。\n\n现在，你已经有了一个 Power BI Desktop 报表，可以与他人共享。 有几种方法可以共享你的工作。 你可以像任何其他文件一样分发 “.pbix” 文件报表，你可以从 Power BI 服务上传 “.pbix” 文件，也可以直接从 Power BI Desktop 发布到 Power BI 服务 。 必须拥有 Power BI 帐户才能将报表发布或上传到 Power BI 服务。\n\n要从 Power BI Desktop 发布到 “Power BI” 服务，请在功能区的 “主页” 选项卡中，选择“发布” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_syw_1.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_syw_1.png)\n\n系统可能会提示你登录 Power BI 或选择一个目标。\n\n当此发布过程完成时，你将看到以下对话框：\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_syw_3.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_syw_3.png)\n\n在 Power BI 中选择打开报表的链接时，报表将在 Power BI 站点中的 “我的工作区”>“报表” 下打开 。\n\n另一种共享工作的方式是从 **Power BI** 服务内加载它。 转到 *[https://app.powerbi.com](https://app.powerbi.com/)* 以在浏览器中打开 Power BI。 在 Power BI“主页” 上，选择左下方的 “获取数据”，可开始加载 Power BI Desktop 报表的过程 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata1.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata1.png)\n\n在下一页上，在 “文件” 部分选择“获取” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata2.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata2.png)\n\n在下一页上，选择 “本地文件”。 浏览到你的 Power BI Desktop“.pbix” 文件并选择，然后选择“打开”。\n\n导入文件后，可以看到它在 Power BI 服务左窗格中的我的工作区”>“报表” 下列出 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata4.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/pbi_gsg_getdata4.png)\n\n选择该文件时，将显示报表的第一页。 可以在报表左侧的选项卡中选择不同的页面。\n\n你可以在 Power BI 服务中通过从报表画布顶部选择 “更多选项”>“编辑” 来更改报表 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share4.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share4.png)\n\n要保存更改，请选择 “文件”>“保存副本” 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg-share-file-save-copy.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg-share-file-save-copy.png)\n\n你可以在 Power BI 服务中，从你的报表创建各种有趣的视觉对象，并可以将该报表固定到 “仪表板”。 要了解 Power BI 服务中的仪表板，请参阅[设计出色仪表板的提示](https://docs.microsoft.com/zh-cn/power-bi/create-reports/service-dashboards-design-tips)。 有关创建、共享和修改仪表板的详细信息，请参阅[共享仪表板](https://docs.microsoft.com/zh-cn/power-bi/collaborate-share/service-share-dashboards)。\n\n要共享报表或仪表板，请在打开的报表或仪表板页面的顶部选择 “共享”>“报表”，或者在“我的工作区”>“报表” 或“我的工作区”>“仪表板”列表中，选择报表或仪表板名称旁边的 “共享” 图标 。\n\n完成 “共享报表” 或“共享仪表板”屏幕中的信息，以发送电子邮件或获取链接，与他人共享报表或仪表板 。\n\n![https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share6.png](https://docs.microsoft.com/zh-cn/power-bi/fundamentals/media/desktop-getting-started/gsg_share6.png)\n\n你可以使用 Power BI Desktop 和 Power BI 服务来制作各种与数据相关的惊艳混搭和可视化效果。\n\n## 后续步骤\n\nPower BI Desktop 支持连接到诊断端口。 诊断端口允许连接其他工具并执行跟踪以进行诊断。 使用诊断端口时，不支持对模型进行任何更改 *。* 更改模型可能会导致损坏和数据丢失。\n\n要详细了解 Power BI Desktop 的多种功能，请查看以下资源：\n\n- [Power BI Desktop 中的查询概述](https://docs.microsoft.com/zh-cn/power-bi/transform-model/desktop-query-overview)\n- [Power BI Desktop 中的数据源](https://docs.microsoft.com/zh-cn/power-bi/connect-data/desktop-data-sources)\n- [连接到 Power BI Desktop 中的数据](https://docs.microsoft.com/zh-cn/power-bi/connect-data/desktop-connect-to-data)\n- [教程：使用 Power BI Desktop 调整和合并数据](https://docs.microsoft.com/zh-cn/power-bi/connect-data/desktop-shape-and-combine-data)\n- [Power BI Desktop 中的常见查询任务](https://docs.microsoft.com/zh-cn/power-bi/transform-model/desktop-common-query-tasks)\n"
  },
  {
    "path": "_posts/2022-04-06-以下为发表在公众号及知乎上的旧文章.md",
    "content": "部分内容有所更新。\n"
  },
  {
    "path": "_posts/2022-04-07-这是我在GitHub上传的第一篇文章.md",
    "content": "我是学谦。\n\n终于，我有了自己的网站。\n\n[powerbipro.cn](http://powerbipro.cn)\n\n记住它！\n\n一个搭建网站的纯小白，也可以在github的强大助力下，轻松实现了这一切。\n\n\n\n"
  },
  {
    "path": "_posts/2022-04-08-Extreme DAX中文第0章  前言.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-extremedax.jpg\ncatalog: true\ntags:\n    - Extreme DAX\n---\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n\n\n![640](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/640.png)\n\n\n\n`Power BI 学谦\n\n\n\n翻译，是个体力活。\n\n我本来觉得机翻至少可以帮助我解决30%的工作量，可是真正把一段又是倒装又是定语从句的英文用汉语顺畅地翻译出来，并且表达出原汁原味的意思，还是得靠自己一个字一个字地敲出来。能轻松地阅读英文原版书籍和逐字抠清楚每句话到底是什么意思之间还是有较大的距离。\n\n接下来一段时间，学谦会和大家一起学习这本DAX的进阶指南——Extreme DAX，共同提高Power BI水平。\n\n受限于本人的能力，翻译总是避免不了各种瑕疵与问题，也请大家能够批评指正，多多提出宝贵意见。`\n\n\n\n**关于作者**\n\n**Michiel Rozema**是世界顶级 Power BI 专家之一，现居荷兰。拥有数学硕士学位，并在IT行业担任顾问和经理超过25年。Michiel 在微软（荷兰）担任数据洞察负责人已有8年，在此期间，他将Power BI在国内发扬光大。他写过两本关于 Power Pivot 和 Power BI 的荷兰书籍。Michiel 是荷兰 Power BI 用户团队的创始人之一，也是Power BI 暑期学校的组织者，并在许多有关 Power BI 的会议上发表演讲。自2019年以来，他一直被授予微软 MVP 荣誉，并与同样是 MVP 的 Henk Vlootman 一起经营着专门从事 Power BI 的咨询公司 Quanto。\n\n**Henk Vlootman**是 Power Platform、Power BI 和 Excel 高级全球业务顾问。自 2013 年以来，Henk 每年都因其杰出的专业知识和社区领导力而获得微软 MVP 荣誉。Henk 同样是荷兰 Power BI 用户团队的创始人之一，Power BI暑期学校的组织者，并曾在世界各地的许多 Power BI 会议上发表演讲。他曾写过两本关于Excel 的书籍和两本关于 PowerPivot/Power BI 的书籍。他于1992年创办自己的公司并开始了他的职业生涯，然后担任 Excel 顾问。如今，他与Michiel Rozema 一起经营着专门从事 Power BI 的咨询公司Quanto。\n\n**关于审稿人**\n\n**Greg Deckler**是微软 DataPlatform 的 MVP，也是美国俄亥俄州哥伦布市 IT 社区的活跃成员，他创立了哥伦布市Azure ML 和 Power BI 用户组（Columbus Azure ML and Power BI User Group，CAMLPUG），并在全国各地的许多会议和活动中发表演讲。Greg 是一位活跃的博主和社区成员，他有兴趣帮助 Power BI 的新用户，积极参与Power BI 社区，提交了 180 多个 Power BI 快速度量库内容并回答解决了 5000 多个社区问题。Greg 是区域咨询公司 Fusion Alliance 的云服务副总裁，帮助客户从云和 Power BI 等云优先技术中获得竞争优势。Greg 撰写了三本关于 Power BI 的书籍：Learn Power BI、DAX Cookbook 和 Power BI Cookbook（第二版）。最后，Greg还为 Power BI Desktop 构建了一个名为 Microsoft Hates Greg's Quick Measures 的外部工具，他还经常在 YouTube 上发布 Power BI 视频。\n\n\n\n`Power BI 学谦\n\n\n\n本书两位作者：Michiel 和 Henk 都是业内顶级专家。他们运营的公司Quanto的博客地址为：https://quanto-blog.eu/\n\n审稿人 Greg 的个人博客地址：https://gregdeckler.com/`\n\n\n\n**前言**\n\n我们可以保证，认真学习完这本书，您的Power BI技术和使用Microsoft一系列工具进行数据分析的能力会有一个质的飞跃。您将发现 DAX 的真正力量，并了解如何为实际业务场景构建高级 DAX 解决方案。\n\n \n\n**本书适合那些人群**\n\n如果您是一名分析师，具备 Power BI 或其他 Microsoft 分析工具中的 DAX 应用知识，本书将帮助您升级 DAX 知识并更有效地使用分析模型。\n\n*注意：这本书并不适合初学者，读者需要有 DAX 的实践经验。*\n\n \n\n**本书涵盖的内容**\n\n**第一部分：简单介绍。**\n\n**第1章商业智能中的DAX**，讨论了商业智能领域以及分析模型在现代 BI 解决方案中的核心作用。得益于DAX 的强大功能，Power BI 模型非常适合用作此类模型。\n\n**第2章模型设计**，讨论 Power BI 模型的基本概念。你将了解 Power BI 模型与其他数据管理产品的根本区别是什么，最后我们将向您展示PowerBI 模型的最佳设计方案。\n\n**第3章使用 DAX**，总结了 DAX 在 Power BI 模型中的不同用法：计算列、计算表、度量值、安全规则和查询。我们还为您提供了一些使用 DAX 的最佳实践。\n\n**第4章上下文和筛选**，涵盖行上下文、查询上下文和筛选上下文，以及上下文在 DAX 公式评估中所起的作用。我们讨论了如何使用CALCULATE函数通过删除原有筛选并将新的筛选添加到现有上下文来转换上下文。此外，我们还会介绍时间智能函数、DAX 表函数、表和筛选器之间的深层关系以及 DAX 变量。\n\n所有这些都是使用 DAX 进行更高级分析的基本概念。在这一重要的章节之后，本书第二部分的重点是将以上讨论的所有概念应用到实际业务案例中，其中许多案例基于我们多年来从事的项目。\n\n**第二部分：商业案例。**\n\n**第5章DAX 安全性**，多方面阐述如何保护 Power BI 模型以及在此过程中 DAX 起到的强大作用。我们通过结合使用建模、DAX 和行级别安全性来讨论行级安全性的多样性、安全角色以及保护层次结构、属性和聚合级别。\n\n**第6章可视化动态展示**，介绍了如何使用辅助表和SWITCH函数来捕获用户输入。我们将演示如何使用DAX动态地更改数据连接以创建高度动态的视觉效果。根据您的预期用途，辅助表可简可繁，可以小到只包含几行选项，也可以是基于 Power BI 模型中其他数据的更大列表。\n\n**第7章备用日期表**，向您展示了当您的日期表看起来与 Power BI 模型默认的标准日历不同时如何实现时间智能。在本章结束时，我们会提供一个 Power BI 报告中相对日期筛选器的替代方案，该方案更加灵活，也可以处理非标准日历中的选择。\n\n**第8章使用自动匹配 AutoExist**，重点介绍了为从 Power BI 模型填充视觉对象该进行*哪些* 计算。了解 AutoExist 的工作原理将帮助您找出为什么有时在视觉效果中看不到预期的结果。它还有助于避免由于在一个视觉对象中使用过多表中的过多列而导致的报表中的性能问题。\n\n**第9章公司间业务**，讨论了两个主要的业务挑战：公司间业务和合并视图，以及针对未结销售订单发送的发票。我们将讨论如何全面跟踪上下文、如何根据可视化效果设置 DAX 度量值，以及进行高级分析的策略。\n\n**第10章探索未来：预测和未来价值**，向您介绍用于分析投资未来的财务指标。我们讨论了未来价值、现值、净现值和内部收益率的通用指标，以及它们在 DAX 中的计算函数，包括XNPV和XIRR。我们还介绍了假设参数（what-if parameters）以及如何在复杂计算中使用它们。\n\n**第11章库存分析**，涉及分析库存数据，同时本章中的分析可以应用于各种面向状态的数据。我们讨论了对此类数据建模的不同方法，如何计算某个时间点的库存状态，以及如何将实际值与目标值进行比较。您还将看到展望未来的不同方式，包括 DAX 中的线性回归。\n\n**第12章人员规划**，讨论了在开展项目时根据全职等效人员（full-time equivalents，FTE）分析人员需求的方法。从技术角度来看，您将学习使用多个事实表的方法，这些事实表必须结合起来考虑以提供有用的结果。我们所面临的挑战不仅在于得出正确的结果，还在于如何找到计算这些正确结果的最佳方法。\n\n\n\n`Power BI 学谦\n\n\n\n阅读本书需要有一定的DAX基础，因此建议大家先阅读并实操一段时间的Power BI相关数据，推荐阅读高飞老师翻译的《DAX权威指南》。\n\n本书的重点是第二部分的商业案例，都是可以直接服务于实际业务的经典案例。当然，第一部分的介绍同样十分精彩，作者提纲挈领地将数据分析的过程拆分为五层模型，并系统阐述了为什么自助式BI是商业智能发展的必然结果。\n\n后续译文会逐步在本公众号与大家进行分享，请大家持续关注学谦。`\n\n"
  },
  {
    "path": "_posts/2022-04-08-Extreme DAX中文第1章  商业智能中的DAX.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-extremedax.jpg\ncatalog: true\ntags:\n    - Extreme DAX\n---\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n\n\n# 第1章  商业智能中的DAX\n\n毫无疑问，**信息**是当今世界上任何一个组织最宝贵的资产之一。作为消费者，我们随时随地都可以感受到各种企业和平台正费尽心机地获取我们的个人数据。不是因为我们每一个人都很有趣（尽管我们确信正在读这本书的您，一定是一个非常有趣的人！），而是一旦将大量消费者的个人数据组合在一起加以分析，组织便可以获得一系列有价值的见解来推动他们的业务向前发展。\n\n这不仅适用于商业公司。公共机构、医院和大学也可以从信息中受益，以更好地运转核心流程。信息是当今世界进步和创新的基础，人们对此有着普遍的共识。\n\n但是要从数据中获取有用信息，再将信息转化为见解和洞察却是一个枯燥乏味且技术性要求较高的过程。整个过程包括：将不同来源的数据进行整合，挖掘隐藏的结构和相关性，与此同时还需要考虑数据产生的实际环境。这就是为什么长期以来商业智能（Business Intelligence，BI）或数据分析领域只有专业IT人员才能胜任的原因。然而，一个需要扎实的业务能力和商业洞察力的过程，却只能由完全不懂业务的IT人员来实现，这显然是不合理的。矛盾就此产生。\n\n本书讨论的核心是：**DAX**（**Data Analysis eXpressions**，数据分析表达式），它是当今数据分析领域最热门的工具之一，也是解决上述矛盾的有力武器。我们假定：作为本书的读者，您对 DAX 有一定的经验，并且希望提高自己的技能。需要特别强调的是，您必须得清楚DAX适合做什么，不适合做什么，这很重要。此外，如果某个过程可以用DAX做得更好，就要尽量避免用其他方式去做。\n\n本章主要阐述一些基本概念，这些内容对您后续的阅读会起到帮助。本章涵盖以下主题。\n\n•   商业智能的五层模型。\n\n•   企业级BI 和最终用户 BI。\n\n•   DAX的优势与使用位置。\n\n•   用于DAX建模的工具。\n\n•   由DAX驱动的可视化与交互式报告。\n\n•   如何开发解决方案。\n\n•   数字化转型循环。\n\n \n\n## **1.1 商业智能的五层模型**\n\n为了在探讨商业智能数据分析时能够更加系统全面与条理清晰，我们开发了一个简单的框架，用来阐述一套数据分析方案的主要组成部分和的各部分的主要作用，如图1.1所示。我们给它起了一个同样简单的名字：**五层模型**。\n\n![image-20220409174132693](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174132693.png)\n\n图1.1 商业智能的五层模型\n\n“五层模型”的第一层也是最低层，**数据连接**层，是数据分析的起点。俗话说，巧妇难为无米之炊，想要对数据进行分析，首先您得先有数据。数据来源有很多：可以是 Excel 工作表、文本文件、大型业务数据库中或网络上的某个位置。\n\n一般来说，这些原始的数据并不能直接进行分析，因为它们的格式往往不符合标准，尤其是当它们来自不同数据源时。因此，您需要先对数据进行一些基本准备工作，也就是第二层：**数据预处理**层。数据预处理有多种形式，像更改数据类型、转换数据、构建数据历史记录或基于“键”值合并查询数据等都是常见的方式。\n\n在连接数据和数据预处理这两个过程中，创建出整齐干净、格式标准的数据集往往需要花费大量时间和精力。有些时候真的让人心力交瘁。在常规的IT领域，打造数据仓库是典型的数据预处理环节，而这，通常会导致开发项目持续多年才能完成。然而让人更加沮丧的是，当数据仓库终于完成时，世界早就不是原来的模样，多年的心血已无法满足当前的实际业务需求。\n\n以上的两步工作结束时，理想的情况是：所有的数据都按照标准的格式存放在模型中，接下来便可以开始对数据进行恰当的分析，也就是第三层：**建模分析**层。这正是数据分析解决方案的核心。通过建模分析，您可以对数据进行切片和筛选，进行各类聚合，并添加各种计算以得到特定的见解。\n\n第四层，**可视化**层，主要是创建报表和仪表板，将建模分析的成果可视化展示。我们将这一层称之为可视化，而不仅仅是“输出”，是因为虽然成果很重要，但是让用户关注到那些重要结论同样重要。可视化真正要实现的是提供见解，或者说“洞察”。由一页又一页的详细信息组成的传统报表并不能让人直观地得出结论，反而会让用户将整个报表导出到 Excel 中，然后自行聚合数据进行分析。真正提供洞察的可视化，往往是一针见血地揭示出核心问题，同时让使用者可以在更低粒度、更详细的指标上进行更深层次的分析，然后找到问题解决方案。\n\n“五层模型”的顶层，**共享**层，由一系列平台与流程组成，旨在为相关人员提供对报表和仪表板的访问权限，同时阻止无关人员的访问。\n\n在构建数据分析解决方案的过程中，无论你是使用 Excel、Power BI、自助开发的企业BI系统，还是根本不使用自动化系统，您都会以某种方式涵盖这五个层面的内容。一个优质的数据分析方案，它的每一层之间界限分明，各司其职。这样做有很多好处，比如可以避免大量的重复性逻辑工作。恰当地实施“五层模型”可以相对容易地应对各方面的变化，比如数据源系统的更改。\n\nDAX 位于“建模分析”这一层，且与“数据预处理”和“可视化”层都有很强的联系，本章将详细展开说明。我们也会在单独的部分专门讨论可视化，但首先，让我们讨论一下这个问题：*谁在**BI**中做什么？*\n\n \n\n## **1.2 企业级BI和最终用户BI**\n\n企业和组织愈加受到数据的驱动。关键绩效指标（key performance indicators，KPI），几乎每一个组织和每一家企业都在使用，通过一系列仪表板来展现每一个KPI指标的完成情况，相当普遍。这些仪表板通常是高度标准化的：组织往往已经对业务战略、业务流程、如何衡量 KPI 以及如何报告它们非常明确。KPI自动化仪表板通常由IT部门或BI中心构建和维护，它们相对稳定，一般不会发生太大变化，\n\n数据驱动型组织的更高层次是，组织做出的*每一项* 决策都是基于相关数据分析得出的结论。这意味着组织需要一个更加灵活动态的数据分析方法，可以随时随地回答各种临时问题，这种形式的数据分析被称作自助式商业智能（self-service BI，**自助式****BI**）。自助式BI旨在让每个人都能在无需IT中心部门帮助的情况下构建BI解决方案。不过，从五层模型中可以清楚地看出，这并不太现实：想要解决五个层面所有的复杂问题，需要同时具备足够的能力与充足的时间，满足这个条件的最终用户寥寥无几。理想的情况是IT中心部门和最终用户各自发挥自己的特长，彼此合作。\n\n我们使用**企业级****BI**（enterprise BI）和**最终用户****BI**（End-user BI）这两个术语来区分数据分析的这两种形式。**企业级****BI**由IT部门构建和维护，使用大型服务器或云平台，并同时为众多用户提供服务。这通常是一个专业的管理项目，它更侧重于数据质量、平台的可用性和流程的明确定义。最终生成相关的解决方案。\n\n**最终用户****BI**由参与实际业务的用户完成。他们希望在日常工作中可以快速通畅地获取见解，他们对此有强烈的需求。有相当多的人一开始是在Excel中建模分析，但是在复杂的Excel模型中，想要保持数据最新以及维护和扩展解决方案，需要用户投入的时间会越来越多。客观地讲，Excel无法从容应对不断增长的数据量。为了解决这个问题，微软扩展了Excel的功能，引入了Power Query和Power Pivot这两个数据预处理和分析的新工具。这两个强大的工具让Excel成为真正的最终用户BI平台。这些工具经过演变成为了现在的 **Power BI**。\n\n**Power BI**，作为微软的数据分析大杀器，其强大之处在于，将企业 BI 和最终用户 BI这两个矛盾的事物有机地结合在一起。Power BI的底层技术是实现这一目标的驱动力。正如图1.2所展示的那样，借助于Power BI，我们现在可以将企业BI和最终用户BI这两个以前对立的形式统筹到一个体系中，无论用户拥有哪种层次的自助分析能力。\n\n![image-20220409174144030](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174144030.png)\n\n图1.2 企业级BI与最终用户BI的结合\n\n“五层模型”是定位不同自助分析能力的有效框架。那些对于自助式BI有需求的用户可以结合该模型的不同层级来认知自己的能力，从而实现他们的目的。他们可以选择在现有的（企业级）分析模型的基础上创建自己的可视化效果；可以使用企业已经预处理完毕的数据集来自助建模分析；或者自行收集数据，将其与企业数据相结合，并自助创建大部分解决方案。他们甚至可以基于其他最终用户创建的模型来进行建模分析。\n\n应该清楚的是，沿着五层模型逐渐向下移动，需要自己承担的比重会越来越大，那么工作的复杂性也就会急剧增加。这样就导致了不仅需要更专业的知识，而且还增加了遵守公司准则和数据标准的责任。此时，就需要企业的IT或BI部门来帮助这些最终用户创建自己的解决方案。如果实施得当，理想的结果是组织中的所有人都能以最佳方式从见解中受益。我们将此愿景称之为**集体分析**。\n\n为了实现集体分析这一美好的愿景，Power BI提供了多项功能，DAX就是其中之一。DAX凭借其强大的实力赋能业务人员，不仅可以使业务人员自助建模分析并找到解决方案，还可以让他们有能力积极参与到企业级解决方案的开发当中。对于后者，我们将在本章的*如何开发解决方案* 这一部分中展开讨论。首先，还是让我们一睹DAX的真容，以及在 BI解决方案中何处可以发现它的踪迹。\n\n## 1.3 DAX的优势与使用位置\n\n在微软的数据分析解决方案中，DAX主要被用于建模分析层面。它在数据分析模型中的作用，是作为公式语言来定义模型中的各种计算和其他逻辑。事实上，模型与DAX实际上是同一枚硬币的两面：模型的设计方式会影响DAX语句的复杂程度，反过来，您的DAX技能也决定着模型设计的难易程度（我们将在*第2章 模型设计*中详细阐述数据模型的核心概念）。\n\nDAX的强大之处在于其高超的数据聚合能力。DAX语言包含众多函数和结构用于定义各种聚合，用户可以从聚合结果中获得所需的见解。长久以来，许多类型的聚合运算必须先通过一系列复杂而又专门的数据预处理来实现，而不能直接进行。比如，想要计算年初至今的销售总额，在 DAX 中仅仅使用一个函数（YTD）就可以实现，而在 Excel或传统报表工具中，需要一系列额外的指标来确定哪些销售交易属于年初至今这个期间，数据预处理环节耗费了大量的时间。后者不仅实现起来更加复杂，而且最终实现的成果还远不如使用DAX那样灵活，DAX不仅可以直接计算年初至今的销售额，还可以同时计算出以往年份的年初至今数据。\n\n这意味着，相比于传统的 BI 解决方案，借助于DAX，我们在数据预处理环节上可以省去大量的时间与精力。由于 DAX 的语法和许多核心概念与Excel很相似，因此对于熟悉Excel的用户来说，DAX 语言学习起来相对比较容易。但是，这并不意味着您可以轻松地*掌握* DAX：在使用DAX的过程中，当您解决了一些稍微简单一些的问题之后，您会逐步将其用于解决更加复杂的问题，但同时您也将为之写出更复杂的 DAX 代码来解决这些问题。本书将为您提供许多 DAX 高级应用的示例，我们希望这些例子能够帮助您去解决遇到的 DAX 难题。\n\n当前，在微软所有的核心数据产品中，我们都可以使用DAX来做建模分析。不过，让人感到疑惑的是，在不同的产品中，模型的命名方式却不太一样。下面，我们将对微软的不同产品中的模型和 DAX做一个基本的概述。\n\n### 1.3.1 Excel中的DAX\n\nExcel 自2010版开始，就以 **Power Pivot** 的形式提供分析数据建模功能。Power Pivot（在 Excel中也称为**Data Model，数据模型**）是基于 DAX 的分析模型。\n\n### 1.3.2 Power BI中的DAX\n\nPower BI是微软数据分析平台上近些年最闪耀的新星。它最初是作为Office 365的一个加载项被引入的，但在2015年升级为一个单独的服务。Power BI 中的分析模型称为 Power BI 数据集（一般简称为数据集），这是 DAX 的栖身之所。\n\nPower BI 数据集和其他的 Power BI 项目是在Power BI 云服务中运行的，用户可通过 Power BI 网站进行访问。同时，其他一些方式也可以使用 Power BI 服务，例如 Power BI 移动应用、Microsoft Teams，甚至如果有能力自助开发，可以通过使用 Power BI Embedded 嵌入自助开发的应用程序来访问。除此之外，如果用户不想使用Power BI的云服务或者企业基于数据安全考虑而不能使用云服务，那么可以选择本地化的Power BI报表服务器。\n\n### 1.3.3 SQL Server Analysis Services中的DAX\n\nSQL Server是微软的数据服务器平台，它包含一个名为Analysis Services（SQL Server分析服务，SSAS）的分析组件。SSAS自2000年左右作为一项OLAP（Online Analytical Processing，联机分析处理）服务开始，多年来一直是一款经典的OLAP服务器，现在被称为**多维模型**（Multidimensional）。随着SQL Server 2012 的发布，SSAS引入了第二个分析功能，称为**表格模型**（Tabular），它是基于DAX的分析模型。\n\n您可能很想知道SSAS中这两种技术之间的差异。本书不准备深入探讨这里所有的细节，但仍然需要指出的是SSAS 多维模型是基于“经典”关系型数据库技术而生。这项技术的设计初衷并非为了对大数据进行聚合与运算，多维模型或数据集只会在建模过程一开始就将所有这些聚合计算执行完毕。相反，在表格模型中，DAX能够即时聚合运算，这意味着用户可以更动态地分析，因为报表设计不会因为模型设计的聚合程度而受到限制。因此，多维模型的灵活多变程度远不如表格模型。\n\n### 1.3.4 Azure Analysis Services中的DAX\n\nAzure Analysis Services （Azure分析服务，AAS） 是一种完全托管的数据分析云服务，与SSAS一样，基于Tabular引擎。显然，与SSAS的不同之处在于，AAS运行在云上，这样您的组织不必担心硬件和数据库的维护。而且它也是一个灵活的解决方案，因为存储和计算资源可以动态扩展以满足当下的需求。\n\n| ![image-20220409174155078](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174155078.png) | 综上，在不同的工具里，含DAX的分析模型以诸多不同的名称存在：Power Pivot、Data Model、dataset 或 Tabular Model。  所以当我们在谈论到“分析模型”这一概念时，很容易产生混淆，这对本书来说是一个挑战。由于本书着重于Power BI，因此我们将在本书中使用**Power BI模型**这个术语，或者在不会产生混淆时直接简称为**模型**。并且，本书中的“分析模型”这一术语仅用来表示“五层模型”中的建模分析层。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n## 1.4 用于DAX建模的工具\n\n根据建模平台的不同，您可以使用以下所列不同的工具来进行DAX建模。\n\n•   对于 Power Pivot 模型，可以在Excel中使用Power Pivot加载项。\n\n•   对于 Power BI 数据集，请使用 Power BI Desktop。\n\n| ![](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174155078.png) | 有趣的是， Power BI Desktop 实际上有三个版本。一个是从 Power BI 网站下载。另一个是从 Windows 应用商店安装的，并且像应用商店中的任何其他应用一样自动更新。当你意识到 Power BI Desktop 几乎每个月都会发布新版本，那么自动更新肯定要方便一些，尽管有些时候新版本可能会更改一些令你意想不到的地方，确实会很烦人。如果需要，可以在同一台电脑上安装这两个版本。第三个版本的 Power BI Desktop（也可以从 Power BI 网站下载）是与 Power BI 报表服务器一起使用的特殊版本。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n•    对于 SSAS 或 AAS 中的 Tabular 表格模型，可以使用 Visual Studio，它为专业开发提供了许多功能，例如与版本控制系统的集成、脚本编写和兼容性。\n\n•    对于 Power BI Premium 中的 Power BI 数据集，可以在 Power BI Desktop 和 Visual Studio 之间选择合适的方式。这可以通过 XMLA 终结点技术实现，XMLA 终结点是 Power BI Premium中实现的一种技术，可为 Power BI 数据集提供与 Tabular 表格模型完全相同的可视化效果。\n\n•    此外，还有几种基于社区的工具，如 Tabular Editor和 DAX Studio。这些工具甚至可以集成到 Power BI Desktop 中。\n\n在本书中，我们选择使用\"朴实无华\"的Power BI Desktop，因为这是一个你应该已经安装了的免费应用。本书的每一位读者都可以轻松下载 Power BI Desktop，并使用异步社区本书页面上存储的示例文件。\n\n## 1.5 由DAX驱动的可视化与交互式报告\n\n在讨论五层模型时，我们已经简要地谈到了可视化报告的重要性。通过以下几个示例您可以发现，想要得到有价值的可视化报告，还得借助于DAX建模来实现。\n\n创建可视化报告的目的是为了得到可靠的分析结论，并提供给使用者直接的见解与解决方案，而不仅仅是大量信息的罗列堆积。我们来看看图1.3展示的这个例子。\n\n![image-20220409174206888](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174206888.png)\n\n图1.3 表中的部分销售数据\n\n你能一眼就发现这家公司存在的问题或者机遇吗？如果能，那么您对于数字一定十分敏感！然而，大多数人更习惯于视觉的直观感受。图1.4是同一组数据以柱状图的形式展示。\n\n![image-20220409174214160](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174214160.png)\n\n图1.4 更直观地呈现相同的销售数据\n\n从柱状图中可以很明显看出，其中一个SKU的销售表现明显优于其他SKU（Stock Keeping Unit，库存量单位，针对电商而言，指的是识别商品的品项）。这是一个值得注意而且很有价值的见解：如果我们能够使其他SKU达到相同的销售表现，那么公司的整体业绩必将大大改善。这个见解会让人情不自禁地继续往下想：是什么导致了这个SKU卖的这么好呢？是否有某个大客户专门订购了这个SKU？这个SKU是否只在某些特定的地区卖得特别好？它的利润情况怎样呢？它卖得这么好，会不会是因为我们的定价太低了？\n\n以上反映了一个基本的因果关系：每当您通过可视化报告得到一些见解时，这些见解又会让您产生更多新的疑问；解答这些疑问的过程生成新的见解，反过来又产生了其他问题。如此循环。\n\n如何解决这种影响？传统的方法是在一份报告中提供尽可能多的信息。原因很简单，提交一项报告需要花费不少时间。Power BI 却以一种完全不同的方式实现这一点，得益于DAX的强大功能，Power BI在报告报告中添加了交互功能，如图1.5所示。\n\n![image-20220409174222530](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174222530.png)\n\n图1.5 可视化-交互周期\n\n交互功能允许报告的使用者深入挖掘初始见解并找到后续问题的答案，从而将一个简单直接的报告转变为有机的交互性报告。而想要实现交互功能，报表需要有能力实时生成新的可视化对象。对于一个Power BI可视化报表来说，它的所有内容全都来自于Power BI模型，这意味着模型在向报表提供结果时也必须同样迅速。而模型的性能是由其本身的结构和您实施的 DAX 代码共同决定。因此，您的 DAX 代码书写好坏会直接影响着报表的用户体验！\n\n## 1.6 如何开发解决方案\n\n在Power BI 模型和 DAX 的帮助下，业务人员可以更加深入地参与开发 BI 解决方案，这与传统BI很大的区别。很显然，这样的解决方案能够提供深入的洞察，从而更好地提升业务价值。\n\n传统的由IT部门来牵头创建的BI解决方案，首先要着手准备连接到数据源并进行数据预处理。如图1.6所示的那样，循序渐进，没有任何问题。毕竟，如果想得到好的并且有价值的见解，高质量的数据是先决条件。\n\n![image-20220409174233662](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174233662.png)\n\n图 1.6 传统的 BI 解决方案开发过程\n\n此过程常见于企业数据仓库的开发中。拥有数据仓库背后的思想是将组织的所有数据集中存储在一个固定的位置，以此为基础去开发所有的报告。\n\n应该清楚的是，这将会是一个大工程，因为组织中有许多不同部门，不同的部门有不同的系统，不同的系统又会有各种类型的数据。根据传统，数据仓库是以关系型数据库系统作为基础实现的，这意味着企业所有的数据都必须符合数据仓库的数据库结构或模式。\n\n数据的多样性会导致数据仓库的架构高度复杂。此外，每当数据源系统更改或引入新系统时，新系统中的数据必须与数据仓库的架构相匹配，如果不匹配，那么就必须更改数据仓库以适应新数据。而每一次更改都会消耗大量的时间和金钱，因此，数据仓库项目经常因其持续时间太长和成本高昂而广为诟病。以往的许多职业都是建立在数据仓库之上的，不幸的是，许多职业也因为数据仓库而被打破了。真是成也萧何败也萧何。\n\n传统的方法还存在一个更致命的缺陷。当您从接入数据源系统开始，然后沿着五层模型向上逐步开展工作，等到建模分析结束出报告得出结论的时候，您已经错过了实际业务场景需要这些见解的黄金时间。尽管大多数数据仓库项目都**想**把业务需求和对应的实际业务场景包含在流程中，但是实际上，如果以这种方式来实现，在众多的开发项目（也许是绝大多数项目）中，实际业务需求往往会被束之高阁。我们要知道，在整个数据仓库的开发过程中，技术的复杂性实在是太大了，根本无法让业务深入参与其中。经常出现的情况是，当数据仓库最终完成（或者更确切地说，第一次投入生产）时，它已经落后于当时的实际业务需求。\n\n在焦躁地等待企业报告可用的同时，公司内部一些部门往往已经按捺不住急切的心情，部署了“影子IT”来获取所需的见解。他们利用手头现有的工具（通常是Excel）和可以获取的任何数据，自己动手建模分析并得到解决方案。虽然这能在一定程度上满足部门和员工对于分析见解的急切需要，但这对于企业的长足发展并没有利。一旦数据的质量无法保证，那么由此得出的结论是否可靠就值得商榷了。基于可靠性不足的结论做出决策，我想，没有哪一家企业愿意冒这个风险。\n\n### 1.6.1 使用 Power BI 模型加速开发BI解决方案\n\n在五层模型中，Power BI 不仅支持通过自下而上的方式进行开发，而且还支持通过自上而下的方式，图1.7很好地说明了这一点。\n\n![image-20220409174242037](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174242037.png)\n\n图1.7 Power BI支持的解决方案开发方式\n\n通过这种方式，我们不仅可以将 Power BI 用作建模分析得到解决方案的平台，还可以将其作为一个工具来简化项目本身。在这个过程中，您可以充分利用Power BI的这些特定功能：快速创建报表并提供切实的见解，并且无论数据来自于哪里，您都可以从容连接并获取。Power BI模型和DAX发明的初衷就是为了实现上述的功能。\n\n该方式基于两个基本定律如下。\n\n（1）我们并不知道我们到底需要什么。\n\n（2）我们的数据并不规范。\n\n基于这些原则产生的结果是，您不可能在第一次就把它做好。相反，您应该部署一种迭代的工作方式，快速试错并快速改进，然后建立正确的模型。\n\n#### 1.我们并不知道我们到底需要什么\n\n这个定律告诉我们，压根不要指望实际业务人员甚至连你自己都不要指望，能够从一开始就为报告提供标准的规范。如果你曾经接触过并承担了BI项目，那么你会对此深有感触。哪怕是那些拍着胸脯说一切尽在掌握之中的人也会忽略很多细节。\n\n即便他们真的有能力做到面面俱到，不放过任何一个细节，但是只要他们在开发过程中没有完全处于实际业务场景中，那么一定会导致部分解决方案的实施产生偏差。\n\n结果就是，花费大量时间去收集需求或者整理出规范然后再去想尽办法通过审批是徒劳的。你可以确定的是：报告的雏形一定是有问题的。所以更好的方法是尽快将带着问题的报告做出来，然后，马上去改。事实证明，相比于一开始就绞尽脑汁写下所有的细节，指出现有模型中的问题和缺陷然后修修补补要轻松得多。\n\n如图1.8所示，您可以通过多次迭代来将此方法变得正式一些，并在最后使用联合会话来显示原型并收集反馈。（我们喜欢称之为**业务设计会话**来突出它们的本质：它们不是反馈会议或演示，而是共同努力实现正确的结果。）取决于您的Power BI 和 DAX 技能，可能需要两天或更长时间来建立雏形。业务设计会话的结果将作为下一次迭代的输入。\n\n![image-20220409174250032](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174250032.png)\n\n图 1.8 迭代需求捕获\n\n这样，我们就得到了一份真正联合开发的且与业务需求完美契合的报告。更重要的是，此报告和模型是充分利用了五层模型当中的较低层提供的数据而得到的标准规范。\n\n#### 2.我们的数据并不规范\n\n第二个定律对你来说一定并不陌生。我们的数据不可能是规范的！原因很简单，实际业务流程往往异常混乱且复杂，再强大的建模能力也无法模拟出完全相同的流程。\n\n而且，当您考虑到IT系统在设计时考虑到了业务流程应该是什么样子的形象时，很明显，自动化业务流程的系统面临着一个根本性的困境。系统实施严格的数据质量策略，即只能输入符合设计流程的数据（因此无法捕获该流程中的每一个案例），或者该系统提供了捕获业务流程所有实例的灵活性，并且必须允许不适合设计的理想流的数据。\n\n后一种选择是大多数时候选择的。这意味着典型的业务系统允许异常、用户用来输入自定义信息的自定义字段以及不同类型的绕过。因此，来自业务系统的数据并不总是符合您的期望。当您的业务数据位于电子表格或其他文件中时，情况会变得更糟！\n\n在传统的BI解决方案中，凌乱的数据很难检测和解决。原因是，通常，BI系统仅包含聚合数据，或者技术不支持业务用户在详细级别上探索数据。这就是Power BI的用武之地：Power BI 模型的技术非常强大，在许多情况下，数据可以加载到模型中而无需聚合。可视化和交互式报表通过复杂的（DAX）聚合提供见解，同时允许你放大到最深层次的细节。\n\n在 BI 解决方案开发的迭代方法中，前几次迭代后的结果通常充满错误。知识渊博的商人通常能够很容易地发现这些错误。首先，以这种方式发现实现的聚合中的缺陷;但在后来的迭代中，数据的质量会显现出来。能够在Power BI报表中查看详细数据对于推动采用和信任新的BI解决方案有很大的帮助。\n\n## 1.7 数字化转型循环\n\n到目前为止，我们重点介绍了从原始数据到见解所需的内容，以及有DAX强大能力加持的Power BI模型在此过程中的作用。正如我们看到的那样，连接到数据源并准备好数据是获取商业价值的基础，但是更多的商业价值来自于可视化与交互式报表提供的直接见解。\n\n然而，没有哪个组织会因为天天盯着漂亮的报表而变得更好。纸上谈兵是万万不能的，实践才是检验真理的唯一标准。换句话说：想要真正知道BI解决方案到底能带来什么好处，您需要结合报表中的分析见解去指导实际的业务。这还不够，您肯定还需要衡量这些操作到底效果如何，要么是以自动的方式，要么是让用户在某个系统里进行输入反馈。这个过程会再次产生数据。\n\n这会产生一个**数字化转型循环**，或者叫数据驱动的业务改进循环，如图1.9所示。\n\n![image-20220409174259416](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174259416.png)\n\n图1.9 数字化转型循环\n\n图1.9所展示的这个循环的左侧，由数据产生见解的过程，Power BI的确能够一展风采，但是对于右侧的由行动到评估和反馈，它可能就无能为力了。这个部分需要其他的功能来实现，例如通过某些组件来实现数据的输入或更新，以及通过相关技术来实现人员与系统的连接。\n\n正是出于这个原因，微软将Power BI作为一个更广阔平台的一部分，这个更大的平台叫做**Power Platform**。Power Platform为了覆盖整个数字化转型循环，采用了同样的基本设计原则：业务人员处于核心地位，易于进入，并且完全有能力满足苛刻的业务需求。\n\n如图1.10所示，Power Platform由Power BI和它旁边的三个主要组件构成。\n\n![image-20220409174307421](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174307421.png)\n\n图1.10 Power Platform组成部分[[1\\]](#_ftn1)\n\n•   Power Apps 提供了一个低代码的环境，以便开发一些可以在智能手机或Web浏览器上使用的应用。这些应用可用于编辑或添加数据。\n\n•   Power Automate 允许在各种系统、服务和面向用户的应用程序之间实现各种流程的自动化。举个例子，可以由收到的电子邮件触发流（flow），然后请求业务所有者确认，并自动更新数据，紧接着触发 Power BI 模型和相关报表的刷新。\n\n•   Power Virtual Agents提供了一个平台，可以通过一些AI聊天机器人实现与Power Platform的良好交互。有了这些AI机器人，用户可以通过对话式的交互来输入数据或进行相关操作，而不必去学习特定的应用程序界面。\n\n尽管以上这些都是 Power Platform 这个大平台上的独立应用，但它们是紧密结合在一起的。比如，可以在Power BI报表中嵌入Power Apps 应用，这样用户就可以在获取见解的位置直接对数据进行修改，完美符合商业智能要紧随业务环境的要求。同样地，我们可以将Power Automate流嵌入到 Power BI报表当中，以便根据报表提供的见解来采取相应的措施。\n\n## 总结\n\n在本章中，我们讨论了商业智能领域以及分析模型在现代 BI 解决方案中的核心作用。基于DAX的强大功能，Power BI非常适合用作此类模型。\n\n您已经了解了 DAX 的两项功能，它们对 BI 解决方案的设计和开发方式产生了深远的影响。\n\n•   DAX支持对各种数据直接进行复杂的聚合运算；过去，在进行聚合运算之前需要先对数据进行一系列的预处理使之规范化。因此，DAX让我们免于被数据（涉及所有繁琐的工作）所困扰，可以专注于生成业务见解的逻辑上。\n\n•   DAX作为一门编程语言被创建的初衷，就是让那些熟悉Excel的业务人员能够在不同层次上自行开发BI解决方案。这意味着商业智能可以更好地与业务保持一致，确保业务的优先级。\n\n由于 Power BI 模型和 DAX 是同一枚硬币的两面，因此，如何平衡这两者，很考验建模者的水平。有个简单的秘诀是，让DAX去做那些它擅长的工作，而不是在数据中解决这些问题，反之亦然，也就是说，不要使用DAX来进行数据预处理或生成数据。\n\n接下来的几章将详细阐述这些主题：在第2章 “模型设计”中，我们将讨论设计 Power BI 模型的注意事项。第3章 “使用 DAX”将重点介绍如何使用 DAX 获得最佳结果。第4章 “上下文和筛选”将继续讨论此主题，探讨了编写 DAX 计算时要了解的最重要的概念。本书的第二部分包含许多示例，其中大部分都来自实际客户项目，这些示例将带您领略 DAX 的强大功能以及如何在 DAX 和 Power BI 模型之间找到最佳平衡点。\n\n\n\n------\n\n[[1\\]](#_ftnref1) 原书无此图，中文版译者添加"
  },
  {
    "path": "_posts/2022-04-08-Extreme DAX中文第2章  模型设计.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-extremedax.jpg\ncatalog: true\ntags:\n    - Extreme DAX\n---\n\n\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n# 第2章 模型设计\n\n设计优良的分析模型是 DAX 高效运行的前提。在本章中，我们将讨论许多与建模有关的主题，这些主题对于理解性能强劲的模型设计非常重要。\n\n本章中的主题包括以下几个方面。\n\n•   Power BI 引擎的数据存储方式。\n\n•   选择正确的数据类型。\n\n•   关系。\n\n•   模型的结构。\n\n为了构建良好的模型，您可能需要适当地转变一下自己的思维方式。无论您在之前的工作环境中一直习惯于使用Excel，还是更多地接触关系型数据库，当您开始接触 Power BI 时，都不得不做出一些改变。如果您更习惯于使用Excel，那么，想要理解数据分析模型中的“关系”这一概念，恐怕需要花上一段时间；即便您更熟悉关系型数据库，这两者之间也存在着诸多不同。关系型数据库专业人士对于“关系”一词肯定是非常熟悉的，然而 Power BI 中的关系却不等于关系型数据库中的关系，它们之间有着根本的不同。因此，我们将在本章中着重讨论这些差异。\n\n## 2.1 列式数据存储\n\nPower BI 模型的强大功能主要得益于智能的数据存储机制。Power BI模型本质上是数据库，因为与数据库一样，这些模型也对数据进行组织并存储。但是，其内部结构与您熟悉的其他数据库技术有很大不同。\n\n### 2.1.1 关系型数据库\n\n在过去，企业处理数据的方式是使用**关系型数据库管理系统**（relational database management system，**RDBMS**），如 Microsoft SQL Server。一个 RDBMS 中一般有大量的表，每一张表中列的数量都是固定的。每一列都必须具有固定的数据类型，如整数、文本或十进制数字，基于此，RDBMS 可以得出存储单行数据或记录所需的空间，并计算出磁盘上的一个数据文件可以存储多少行。这个特性使得 RDBMS 成为*处理事务*（process transactions）的应用程序的有效选择，例如来自网购平台的销售记录或公司财务分类帐中的记录。\n\nRDBMS 中有一个概念叫做：**索引**（index）。通过索引可以快速而高效地查找特定的记录，这意味着也可以使用 RDBMS 有效地处理现有记录上的事务。关系型数据库这一术语之由来，是因为其中的表可以通过关系来连接，这确保了这些表中的数据是一致的；比如，某个 RDBMS 会阻止来自未知客户的销售交易插入。\n\nRDBMS 技术经过长期的优化与迭代更新，目前已经非常成熟。因此，大多数传统的分析平台也依赖于RDBMS技术。但是，数据分析解决方案的技术需求与一个事务系统对技术的要求完全不同。在进行数据分析时，您往往不会从单个行中检索所有列的数据，相反，您可能对同时从多个行中获取数据感兴趣，并且往往只分析其中的一列或几列数据。然而想要从单个列中检索信息，RDBMS 仍需要从存储中读取一整行数据。同样，RDBMS并不擅长聚合多行数据，因此速度相对较慢。\n\n图2.1对此过程进行了可视化说明：按行存储数据（由数字标识）无法有效地检索需要列的所有值。\n\n![image-20220409173433238](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173433238.png)\n\n图2.1 从基于行的存储中检索列的值效率低下\n\n### 2.1.2 列式数据库\n\n与RDBMS的按行存储数据不同的是，Power BI模型通过按列存储数据来实现这一过程。这背后的基本原理是，在数据分析解决方案中，往往只需要从存储中读取单独的几列，但所有可用的行都得参与计算。当同一列中的所有数据都存储在相邻的位置时，效率很显然是最高的。\n\n另一个原因是，在实际的业务中，单个列中的许多值是相同的；例如，几千或几万种产品往往对应着数以百万计的销售交易记录。此时，列式数据库可以通过仅存储一次特定值并记录它所属的行来高度压缩数据。\n\n列式数据库实现的高压缩率开辟了将整个数据库保存在内存中的可能性，这意味着所有数据都驻留在运行数据库的计算机或服务器的内存中，而不是存储在磁盘上的文件中。而将数据保留在内存中可进一步加快数据检索速度。\n\n列式模型意味着数据聚合异常高效。例如，列式数据库引擎可以简单地获取每个不同的值，然后将其乘以显示该值的行数，而不是对列中所有单独的值求和。简而言之，Power BI 模型的数据库引擎从一开始设计时就旨在支持数据分析的这种典型工作负荷：处理具有特定特征的大量数据，并在这个过程中执行聚合与计算。\n\n但是，需要提醒的是：最终，您仍然需要知道不同列中的哪些值是放在一行中的。仅仅知道编号为103的产品已经全部售出是不够的；您还需要知道它的价格，销售给哪个客户以及销售日期是哪一天。若要实现这一点，模型必须保留指针列表，以跟踪列中的某个值到底位于哪一行中。当向表中添加更多列时，计算量显然会显著增加。因此，在 Power BI 模型中，“窄”的表比“宽”的表更高效。\n\n## 2.2 数据类型和编码\n\nPower BI 模型包含有限数量的数据类型。为数据选择正确的类型非常重要，因为它决定了数据的存储或编码方式，以及模型处理数据的效率。以下是能够被 Power BI 识别的所有数据类型。\n\n•   **文本（Text）**：最常见的数据类型就是文本。几乎所有数据都可以存储为文本。在Power BI 模型中，通过 Power Query 加载数据时，会将所有数据类型统一转换为 Text。很显然，当您忘记在 Power Query 中显式进行类型转换时，数字列也会存储为文本。（当然，你可以更改模型中的数据类型，这将自动在 Power Query 中添加一个更改数据类型的步骤。）\n\n•   **整****数（****Whole Number****）**：正如您所猜测的那样， “整数”数据类型用于存储整数。由于 Power BI 模型存储和压缩数据的方式，这是最高效的数据类型之一。\n\n•   **十进制数字（Decimal Number）**：通常，数据类型为数字的都使用这个格式存储。从非常小的数到非常大的数，以及分数值，它几乎可以存储。最多可以存储 15 位数字。\n\n•   **定点小数（Fixed Decimal Number）**：这种类型用于存储具有固定四位小数的小数值，有时也被称为“货币”类型（Currency）。最多可以存储包括四位小数在内的19位数字。这意味着此数据类型的覆盖范围小于十进制数字类型。定点小数类型通常用于存储货币金额，同时也可用于不需要很多小数的任何值。\n\n•   **日期/时间、日期、时间（Date/Time, Date, Time）**：Power BI 模型使用与 Excel 类似的结构存储日期和时间值。这意味着其值是十进制数字，整数部分表示日期，小数表示时间。\n\n与 Excel的不同点在于基本参考日期：在 Power BI 模型中，数字 1 对应于 1899 年 12 月 31 日，而在 Excel 中，数字 1 对应于 1900 年 1 月 1 日（均在零点）。小数是在此基础上添加二十四小时制的一天中的时间；例如，值 2.5 表示 1900 年 1 月 1 日中午。\n\n您有三种选择来存储日期/时间数据。日期/时间数据类型同时存储日期和时间。日期数据类型仅存储日期，这意味着此数据类型等效于整数。时间数据类型仅存储时间部分，它一直是小数。\n\n•   **真/假（True/False）**：真/假或布尔数据类型只能存储两个值：真和假。虽然使用时限制比较多，但使用此类型可以非常有效地存储数据。\n\n•   **二进制（Binary）**：二进制类型用于存储不能表示为文本的数据，如图像数据或文档。无法使用此数据类型执行聚合或计算，但它可用于存储需要在报表中使用的图像。\n\n为了实现高效的模型，为数据选择合适的数据类型至关重要。Power BI 模型旨在尽可能高效地将一系列唯一值存储在列中。虽然我们在使用计算机时早就不必考虑位和字节的概念了，但是在设计模型时，考虑计算机使用的单个0和1仍然会有所帮助。模型将确定存储值所需的位数；由于所有数据都运行在内存中，因此能节省一些内存就尽量节省一些。\n\n例如，假设有一列都是介于 0 和 10 之间的整数。在数字表示法中，数字 10 表示为 1010 或 4 位。因此，该值便可以用4位的字进行编码，直接表示该值。此方法称为**数值编码（value encoding）**。在配备 64 位处理器的现代计算机中，使用 4 位显然要比将值存储为 64 位的数字要高效得多。\n\n数值编码只对整数有效，因此，整数格式自然可以进行数值编码。但是一些披着其他数据类型外衣但是本质是整数的数据类型，同样也可以使用数值编码：比如日期和布尔值。还有一个是你可能想不到的：定点小数。定点小数由于是**固定**的4位小数：它可以被当成是一个整数除以10000的结果。实际上，DAX 引擎能够在进行数值编码之前先进行基本的转换，例如将所有的值减去相同的数字。\n\n其他数据类型不能直接表示为整数，数据库仍然需要找到一种方法来将这些值存储在最小的位数中。方法是通过保留带编号的值列表并存储数字，而不是直接存储原始值。这称为**哈希编码（hash encoding）**。哈希编码列的工作方式不如数值编码列高效，因为数据库每次使用这一列时都需要在这些数字和值之间进行转换。\n\n需要强调的一点是，Power BI 模型会根据列中的数据类型和值选择最佳编码形式。这意味着，哪怕数据类型是整数或者本质是整数，到最后仍然可能使用哈希编码。举一个极端的例子，有一个数字列，不仅包含0到10之间的数字，还包含数字1,000,000时，直接存储这些值所需的位数比较多，以至于引擎将决定改用哈希编码。\n\n我们在实际项目中经常看到这种情况，特别是在存储日期时。假设您有一个包含员工的表，其中包含他们的入职日期以及离职日期。对于所有在职员工，离职日期当然是空的；但是习惯上我们并不是空着这个字段，而是设置一个特定的未来日期。很多时候这是一种有效的方式，但是如果选择像 9999 年 12 月 31 日这样的日期，则肯定无法享受对日期列进行数值编码的优势。建议使用不太遥远的未来的一天，例如 2029 年 12 月 31 日（当然，具体取决于你的实际方案）[[1\\]](#_ftn1)。\n\n## 2.3 关系\n\nPower BI 模型中一个最容易被误解的元素是关系的概念。当你使用Power BI时，无论你之前是更多接触Excel，还是更加熟悉关系型数据库，你都需要从头开始学习Power BI 模型中的关系。\n\n### 2.3.1 Excel 中的数据\n\n让我们先关注 Excel 中的数据。在 Excel 中最接近数据库的概念是 Excel 表的概念。您可以将 Excel 表视为“扁平的”数据库。这种存储数据的方式有许多缺点。\n\n例如，图2.2显示了某个存储在 Excel 工作表中的数据。\n\n![image-20220409173445145](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173445145.png)\n\n图2.2 Excel中的表格\n\n图2.2展示的表中包含由员工销售订单的订单金额和日期。这样一个扁平的数据库存在诸多问题。\n\n•   显然，有关员工的所有信息（如工作角色和出生日期）都在该员工销售的每个订单中重复。因此，大量信息是冗余的，这占用了大量的存储空间。\n\n•   多次存储信息会增加数据出错的风险。\n\n•   当员工的某些属性（如其工作角色）发生更改时，必须在与该员工关联的所有行中进行更改。\n\n•   当一个实体有多个相同类型的属性时，情况会变得更糟。在我们的示例中，Giuliana 似乎有两个工作角色，并且每个销售订单仅与其中一个工作角色相关联。当我们按工作角色汇总销售额时，顾问（Consultant）的结果将仅包含Giuliana 的其中一个订单，从商业角度来看，有些时候说得通，但是有些时候这样得到的结果是错误的。\n\n•   最大的麻烦可能产生于从多个不同的数据源获取数据时。让我们设想这么一个场景，我们不仅有销售数据，还有目标数据。将来自不同数据源的数据合并到一个扁平的数据表中需要花费大量精力。实际上，Excel 用户将大部分时间花在设置单个扁平的数据表上，以便他们下一步能够使用数据透视表。\n\n在Excel中，这些问题实际上没有解决方法。的确是这样，除非你开始使用 Power Pivot，而它和Power BI模型从本质上而言是等效的。在正式讨论 Power BI 里的方法之前，让我们看一下如何在关系型数据库中处理数据。\n\n### 2.3.2 关系型数据库中的数据\n\n在关系型数据库或 RDBMS 中，数据被分隔到多个表中。通常，这些表通常是关于那些组织的实体（如客户、员工、产品等）。表中的每一行都有一个标识符或**键**（key），可以实现固定地引用其他表中的行；例如，在图2.3所示的销售订单表中，可以只包含客户和产品的键，而无需包含所涉及的客户和产品的所有属性。\n\n![image-20220409173458101](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173458101.png)\n\n图2.3 关系型数据库中的关系\n\n显然，在录入销售订单时，没有客户键或者存在未知键，是没有任何意义的。这就是为什么在关系型数据库中，您需要定义表之间的关系，以表示表中的哪些列指向其他表中的键。关系型数据库会确保定义关系的列仅包含相关表的已知键。如果一条记录未存在于与之相关的表中，那么数据库将阻止其插入或更改。换句话说，关系型数据库中的关系充当对存储数据的约束，并用于强制实施**参照完整性**（referential integrity）。\n\n### 2.3.3 Power BI的关系模型\n\n终于，我们要开始讨论 Power BI 了。在 Power BI 模型中，关系是表与表之间的连接。这么一看，它们应该与关系型数据库中的关系相当，但实际上，它们完全是两个不同的事物。\n\nPower BI 模型中关系的基础是具有唯一键的数据表。具有相同键值的另一个表可以与其相关，但在这个表中，键值不必是唯一的。这种类型的关系称为**一对多**关系，这意味着有一个表的键只出现一次，而另一个表的同一键可以多次出现。同关系型数据库一样，您可以将具有唯一键的列称为**主键列**（primary key column），将具有非唯一键的列称为**外键列**（foreign key column）。\n\n在如图2.4所示的Power BI Desktop中的结构里，我们可以在模型视图中查看 Power BI 模型的结构以及其中的表和关系。\n\n![image-20220409173509317](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173509317.png)\n\n图2.4 Power BI 模型中两个表之间的关系\n\nPower BI 模型中的关系与关系型数据库中的关系之间存在两个根本的区别。首先是参照完整性。关系型数据库中的关系充当数据约束，然而 Power BI 中的关系却并没有这么严格的要求。坦率地说，Power BI 并不在乎你的数据是否一致。当一些值只在外键列出现而不存在于主键列时，关系仍然可以存在。\n\n如图2.5所示，模型会将每个未知的外键的值连接到一个空白行。模型中不会显示这个空白行，但是在报表中会显示。\n\n![image-20220409173516156](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173516156.png)\n\n图2.5 未知值与空白行相对应\n\n这样做的一个优点是，您不必担心加载或刷新数据表的顺序，而在关系型数据库中，这是需要仔细考虑的。当然，缺点也随之而来，那就是在创建关系时必须小心，尤其是在模型视图中通过拖放字段来执行此操作时。当你拖拽字段放在错误的关系目标上时，Power BI 不会报错也不会有任何提示，它只会悄无声息地创建一个没有任何意义的关系。\n\nPower BI和关系型数据库中的关系之间，还有另一个重要区别是筛选器传递（filter propagation）。Power BI 模型中的关系会主动筛选数据。更具体地说，当一个表中的某些行被选择时，另一个表中的相关行也会自动选择（沿着关系的箭头方向）。这是 Power BI 模型的核心设计原则，在进行 DAX 设计计算时需要充分考虑这一点。\n\n而在关系型数据库中，关系并没有此功能。在查询关系型数据库时，用户必须指定要在哪些表上组合哪些（主键和外键）列。这使得查询关系型数据库非常灵活，但同时也迫使数据库为每个查询执行大量的工作。不过，这样导致的结果是，从一系列表中检索数据同时还需要处理大量关系会很低效。\n\n### 2.3.4 关系属性\n\n在 Power BI 模型中的表和表之间创建关系时，可以对驱动其行为的关系设置多个属性。这些属性与关系的主要目的，也就是筛选器传递，直接相关。\n\n#### 1.活动关系和非活动关系\n\n要使关系能够进行筛选器传递，表之间必须存在明确的连接。假设对于销售交易记录，订单日期（order date）和付款日期（payment date）这两列同时存在。如果从这两列到日期表都存在关系，并且在日期表中选择了一行，那么我们在探讨应当筛选哪些销售交易记录时，会产生如下的疑问：是在该日期订购的交易记录，还是已付款的交易记录，还是将两者都筛选出来？\n\n为了处理这个问题，Power BI 模型只允许两个表之间有一个活动的关系存在。当两个表通过其他表连接时，这同样适用：只允许单个活动关系路径。如图2.6所示，它是 fSales 表（销售表）的 Order Date 列（订单日期列）与 Calendar 表（日历表）的 Date 列（日期列）之间的关系。当你创建第二条路径的关系时，之前的关系将变为非活动状态。在模型视图中，非活动关系用虚线来表示。\n\n图2.6展示了继续添加两个关系之后的模型视图：分别在 fSales[Delivery date] 与 Calendar[Date] 两列之间和 fSales[Payment date] 与 Calendar[Date] 两列之间建立关系。\n\n![image-20220409173705790](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173705790.png)\n\n图2.6 一个活动关系和两个非活动关系\n\n在某个特定的计算中，可以使用 USERELATIONSHIP 函数来激活非活动关系，同时原有的活动关系在该计算中暂时失效。\n\n| ![image-20220409173724759](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173724759.png) | 注意：在包含主键的表上定义行级别安全性  （row-level security，RLS） 时，使用 USERELATIONSHIP 函数来激活关系将导致 DAX 计算中出现错误。原因是，同任何其他筛选器一样，安全筛选器是通过关系传递的。停用传递安全筛选器的关系并激活另一个关系会导致对应选择的内容产生歧义。  因此，在设计模型时要小心谨慎，同时对未来可能需要的安全要求做到心中有数。还有一个建议是：不要过度使用非活跃关系。有关模型安全的深入探讨，请查看本书第5章 “DAX 中的安全性”。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n#### 2.交叉筛选方向\n\n通过关系进行的筛选器传递通常仅从主表（primary table）到外表（foreign table）。如图2.7所示，在模型视图中，筛选器传递或者交叉筛选（cross filter）的方向通过关系线中间的小箭头显示。\n\n![image-20220409173718138](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173718138.png)\n\n图2.7 关系的交叉筛选方向\n\n我们也可以更改交叉筛选的方向，让筛选器在两个方向上传递。这个操作是在**编辑关系**对话框中完成的，方法是将交叉筛选器方向设置为**两个**。看上去，在两个方向上进行筛选似乎应该是默认的简便设置，但，不要这样做！实际上，只有在某些特定方案中我们才会使用双向的交叉筛选关系。请尽量避免使用双向关系，否则您的报告中将会出现许多奇怪的现象、许多非活动关系以及高度复杂的 DAX 计算。\n\n使用双向交叉筛选的一个特定场景是在处理多对多关系时。举个例子，假设一个包含客户（customer）和分支机构（branch office）的模型，如图2.8所示。每一个客户由一个或多个分支机构提供服务，反过来，每一个分支机构又服务于多个客户。\n\n![image-20220409173856911](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173856911.png)\n\n图2.8 客户和分支机构\n\nCustomer 表和 Branch office 表都有唯一的键列，但它们都没有包含外键的列：每一行都必须关联到另一个表中的多行。解决此问题的方法是：使用一个包含所有客户键和分支机构键组合的中间表[[2\\]](#_ftn2)，Branch office Customer 表。接下来，可以分别从中间表到 Customer 表和 Branch office 表创建关系，如图2.9所示。\n\n![image-20220409173904203](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173904203.png)\n\n图2.9 中间表\n\n但是，以上的关系并没有正确地从 Customer 表到 Branch office 表进行交叉筛选，反过来也是如此：您可以在 Customer 表中选择一行，关系会将所选内容传递到中间表，但接下来却不会传递到 Branch office 表，因为此关系是单向的。\n\n图2.10给出了解决方案：将两种关系都设置为双向的交叉筛选。此时，在 Customer 表中选择某一行时，左侧的关系将向右传递到中间表，右侧的关系再向右传递到 Branch office 表。反过来，在 Branch office 表中选择某一行时，关系会将所选内容传递到中间表，然后再将该选择传递到 Customer 表。\n\n![image-20220409173914811](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173914811.png)\n\n图2.10 通过中间表实现多对多关系\n\n| ![image-20220409173923576](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173923576.png) | 注意：当您将其中一个表（例如 Customer 表）与包含销售交易记录的表相关联时，此处会出现警告。对于每个销售交易记录，必须记录客户键。该设置允许选择一个客户，比如张三，并查看张三的总销售额。但是，我们无法查看张三在某个特定分支机构的销售额：如图2.11所示，随便选择一个分支机构，只要张三在这里消费过，那么我们只能获取他在所有分支机构中总的销售额。当您按分支机构报告总销售额时，张三的销售额将成为与之相关的每个分支机构销售额的一部分。  在这种情况下，使用中间表将这两个表关联在一起并不是一个很好的选择，通常，我们会将  Customer 表和 Branch office 表直接与销售表相关联，大部分时候，这是最佳实践。  ![img](https://raw.githubusercontent.com/xueqiandata/picgo/main/clip_image024.jpg)  图2.11 在 Branch office Customer 表和 Customer 表以及  Branch office 表之间使用交叉筛选 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n#### 3.基数\n\n模型中的默认关系是**一对多**关系，其中一个表包含一个唯一的主键，另一个表包含与外键相同的值，这些值并不是唯一的。此关系属性的正式叫法是**基数**（Cardinality）。\n\n关系也可以有其他的基数。将一对多关系中的两个表位置换一下就是**多对一**关系。\n\n关系可以具有**一对一**基数，这意味着在关系的两端，键都是唯一的。默认情况下，一对一关系的交叉筛选器方向是两个。因此，在几乎所有情况下这两个表都充当一个表。需要提醒的是，应避免在模型中建立一对一关系：除非有特定原因将它们分开，否则应将两个相关表合并为一个表（想要了解这些原因可能是什么，请参阅第8章“使用 AutoExist”）。\n\n关系基数的最后一个选项是**多对多**。在这种情况下，两个相关表都不包含唯一的键。同样，您可能有特定的理由使用这种关系。但是，我们强烈建议不要使用多对多关系，因为这些关系很容易将你的模型搞得一团糟。本章后面 “在 Power BI 模型中要避免的关系型数据库原则” 部分将详细介绍多对多关系。\n\n## 2.4 高效的模型设计\n\n关系和筛选器传递的概念让 Power BI 模型可以实现强大的分析能力。因此，在建模时，思考模型的设计非常重要：模型应包含哪些表，这些表中需要包含哪些列，需要建立哪些关系？简而言之，模型的整体结构是什么？您在模型设计中所做的选择将决定模型能够达到什么样的效果。\n\n### 2.4.1 星型结构和雪花结构\n\n使用关系型数据库进行数据分析的最佳做法是使用一个特定的数据库结构，称为**星型结构**（star schema），如图2.12所示。星型结构的基本思想也适用于 Power BI 模型。\n\n![image-20220409173934964](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173934964.png)\n\n图2.12 通用的星型架构的结构\n\n处于星型结构模型中心的表是**事实表**（fact table）。事实表包含已经发生、将要发生或应该发生的事情，如销售交易、财务分类账交易、客户查询、学生注册和销售机会等。\n\n通过外键列，事实表与那些描述事实的不同实体（如客户、产品、成本中心、学生、日期等）的表建立关系。在星型结构的概念中，这些表称为**维度表**（dimension table）；但是，在 Power BI 模型中，我们更愿意将它们称为**筛选表**（filter table），原因如下所述。\n\n筛选表中的列被用来筛选报表中的结果，可以将它们用作矩阵或表中的行标签，或者作为图表的轴，又或者将它们作为切片器字段。事实表中包含报告需要进行聚合的数据。每个键值可以在事实表中多次出现，对应于同一天出现的多个事实，或者针对同一客户的多个数据，等等。\n\n在一个纯粹的星型结构模型中，筛选表之间没有任何关系。当筛选表与其他筛选表相关时，生成的模型结构称为**雪花结构**（snowflake），如图2.13所示。\n\n![image-20220409173943755](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173943755.png)\n\n图2.13 雪花结构\n\n### 2.4.2 星型结构的问题\n\n在关系型数据库专业人士的眼中，雪花模式通常被认为是劣质设计。他们通常会花费大量时间和精力来设计纯星型架构。许多 Power BI 顾问都具有关系型数据库的应用背景，他们总是习惯于把在关系数据库中学到的知识迁移到 Power BI 上。因此，在收集有关 Power BI 建模的信息时，你总会听到他们说“生成星型架构！”\n\n本节有些挑衅性的标题是为了讨论 Power BI 建模中真正重要的内容到底是什么。需要说明的一个事实是，Power BI 模型**不是**关系型数据库。然而，Power BI 模型的许多概念和术语却与关系型数据库非常像！这会导致一些 Power BI 模型设计人员将从关系型数据库学到的知识应用到 Power BI 模型当中。结果就是，这样做出来的模型效果欠佳。\n\n需要注意的是，星型结构的概念是在列式数据库出现之前时开发的。关系型数据库的星型结构可最大限度地减少查询数据库时的连接数，这一点很重要，因为关系型数据库在同时联接多个大数据量的表时往往会遇到麻烦。这是数据仓库的典型工作负荷，因为在传统上，数据仓库被当作报告的数据源。我们所说的“传统上”，是指在 Power BI 模型出现之前；如今，数据仓库只是 Power BI 模型的数据源，在将数据导入模型时，根本不需要任何连接。\n\n“为什么使用星型架构？”这个问题通常可以用它的反面来解答，它与“规范化的事务架构”不同。实际上，商业智能需要对许多数据行进行聚合，而事务处理则需要插入或更新单个数据行，同时保护数据的一致性。对于分析而言，基于星形架构的模型绝对是很有必要的。\n\n然而，许多人将“很有必要使用星型架构”翻译为“不要使用雪花结构”。或者，换种说法，每个维度表都应与事实数据表直接相关。虽然在直接查询报表的数据仓库中可能需要这样，但对于 Power BI 模型，不能笼统地这样说。得益于关系的存在，Power BI 模型的技术性能表现非常好，基于此，Power BI 模型对数据的压缩能力非常强大，使用雪花结构不必成为一个大问题。根据经验，在设计模型时，星型结构是一个很好的起点，但是没有必要费心费力地去避免使用雪花结构。\n\n为什么对星型结构与雪花结构进行如此长篇幅的阐述？因为这是将传统数据仓库的想法一股脑地应用于 Power BI 的主要表现。在我们的咨询工作中，我们经常需要与 IT 部门打交道，并且通常需要花费大量的时间和精力来解释 Power BI 与传统的数据库是有着根本不同的。我们特意对 Power BI 解决方案的某些元素使用不同的术语，以强调这些差异，并使业务人员更容易理解。\n\n在下一节中，我们将讨论在进行 Power BI 解决方案建模时，应用传统的关系型数据库和数据仓库的原则会出现的几个问题。\n\n### 2.4.3在 Power BI 模型中要避免使用的关系型数据库原则\n\n在上一节中，我们提醒过不要盲目地将关系型数据库世界中的经验教训应用于 Power BI 模型。下面，我们将讨论几个具体的例子。\n\n### 1.相互依存的维度\n\n什么是维度？在数据仓库中，维度是一个表，其中包含有关存储在事实数据表中的事实的描述性属性。*维度* 这一名词来自数学和物理学中的概念；在这里，维度是描述一个对象的独立参数，如高度或宽度。\n\n这与描述旧的数据分析方法（在列存储解决方案存在之前）的术语“多维建模”一起表明，数据仓库中的维度应该是独立的实体。当然，我们也遇到过多个维度表之间紧密关联的数据仓库结构。举个例子，现在有一个客户（Customer）维度表和一个市场细分（Market Segment）维度表。如果一个客户属于多个细分市场，那么维度之间确实应该是独立的；但在许多组织中，每个客户都属于单个细分市场。\n\n这在数据仓库中谈不上是什么问题，但在 Power BI 模型中还真是个问题。假设你有一个 Power BI 报表，其中包含细分市场和客户的切片器。用户理所当然地期望，当他们选择某个细分市场时，客户切片器将仅显示与所选细分市场相关的客户。换言之，您的模型需要将市场细分维度表上的筛选传递到客户表，反之亦然。使用具有单个交叉筛选器方向的默认一对多关系不会有这个效果，我们需要对关系启用双向交叉筛选，从而生成如图2.14所示的模型。\n\n![image-20220409173954951](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409173954951.png)\n\n图2.14 相互依赖的维度之间需要双向的交叉筛选\n\n但是，当你试图添加第二个事实表时，你会发现这是一个非常不好的 Power BI 建模案例：之前的活跃关系总会有一些变得不活跃，而且不可避免。更好的设计方案是将属于一起的筛选器表进行聚类，并只允许其中一个表与事实表建立关系，并且设置为具有单个交叉筛选器方向。如果需要，可以使用双向交叉筛选器让几个筛选表实现集群（cluster），如图2.15所示。这样做的一大优点是可以省去事实数据表中的多个键列。\n\n![image-20220409174001721](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174001721.png)\n\n图2.15 筛选表集群\n\n| ![image-20220409174007910](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174007910.png) | 在本书中，我们使用筛选表（filter table）这一术语，而不是维度表（dimension table），原因有三个：首先，避免受到传统的关系型数据库建模的影响；其次，因为多个筛选表可能属于统一个集群，导致它们之间并不是独立的；第三，该名称对业务用户更有意义，并且能够更好地与 Power BI 中筛选器的核心概念保持一致。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n当然，有些人会说，可以将集群中的筛选表组合成一个大表，这样模型就会变成一个标准的星型结构。的确可以，但是没有必要非得这样做。此外，还有一些不这样做的理由：您可能有其他不同粒度的事实表需要专门与其中一个筛选表相关联（例如每个细分市场的目标），并且，相比于组合成单个大表，一个一个独立的表更容易被业务人员看懂。即便没有这种业务需要，至少您可以节省一些时间去更好地解决实际业务问题。\n\n### 2.只有一个事实表\n\n这个比较简单：我们经常会遇到一些人，他们听到星型结构需求时，直接将其翻译为“你应当只有一个事实表”。虽然这可以解决许多双向交叉筛选器关系的问题，但是想要创建单一的事实表，需要进行大量的工作，并且会导致事实表中具有太多的列。因此，我们给出结论：在模型中拥有多个事实表是完全没有问题的！\n\n### 3.数据仓库作为单一信息源\n\n从上面的讨论中可以清楚地看出，Power BI 在技术上没有必要使用经过全面设计的数据库架构的（关系型）数据仓库。由于数据仓库仅向 Power BI 模型提供数据表，因此实际上并不需要关系型数据仓库这样的架构优先（schema-first）体系结构。事实上，您可以使用更简单的数据优先（data-first）结构，比如数据湖。\n\n但是，在许多情况下，企业拥有数据仓库才是常态。这通常伴随着“所有业务逻辑都必须在数据仓库中实现”这一口号的要求。从 Power BI 的角度来看，这并不是最好的方式。\n\n该策略的主要缺陷是数据仓库只有一种与外界通信的方式：数据。一个报表通常包含以基本方式聚合或高度复杂的方式聚合的数据（本书的第二部分将仅介绍聚合数据的高级方法）。但事实是，报表中所需要的许多结果无法通过求某一列值的总和或平均值这样标准的聚合来完成。因此，不可能在数据仓库中实现所有业务逻辑。\n\n数据仓库设计的初衷总有一条是为了实现尽可能多的业务逻辑。但问题还是一样，数据仓库只能以数据的形式进行通信。这将会导致事实表包含大量的列，每个列都有特定的业务规则或聚合。但是，你并不希望在 Power BI 模型的事实表中包含如此多的列！\n\n \n\n| ![image-20220409174020112](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174020112.png) | 有趣的是，有一种类似数据库的技术不仅能够通过数据，还能通过聚合逻辑进行通信：Power BI模型！  由于可以通过 DirectQuery 模式连接到 Power BI 数据集，因此可以将 Power BI 模型用作数据和聚合的中心，并从中派生其他模型。换句话说：如果存在单一信息来源，那么大概率会是 Power BI模型。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n### 4.使用多对多关系\n\n您应该不惜一切代价去避免的一件事是：在两个事实表之间建立直接关系。由于事实表很少包含具有唯一值的列，因此一般而言这个关系将具有多对多基数。（不过，如果事实表确实包含具有唯一值或几乎唯一值的列，则应该反思一下，模型是否真的需要这一列。）\n\n多对多关系不仅会因为筛选器传递受阻而导致意外的结果，而且模型的性能也会因此大打折扣。这是因为在这种情况下往往有太多的行是相关的。关系对性能的影响与主键（或关系的“一”端）的唯一值的数量高度相关。因此，不要让这个数字变得太大；根据我们的经验，最好不要超过100,000行。\n\n对于多对多关系，另一个稍微有用一点的案例是将事实表与具有不同粒度的筛选表相关联。例如，您的模型包含一个 Product 表（产品表），其中包含对多个产品进行分组的 Category 列（类别列），销售记录可能存储在产品级别。目标或销售预测可以在产品类别这一级别上给出。Power BI 允许创建目标事实表与 Product 表 Category 列之间的多对多关系，如图2.16所示。\n\n![image-20220409174030269](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174030269.png)\n\n图2.16 使用多对多关系\n\n虽然这个模型可以正常地工作，但我们更喜欢如图2.17所展示的那样，使用包含 Category 唯一值的中间筛选表来实现。\n\n![image-20220409174037854](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220409174037854.png)\n\n图2.17 使用中间表\n\n通过使用中间表，所有结构都是通过常规的一对多关系实现的，这些关系具有一致的行为，并且 DAX 引擎已针对这些关系进行了优化。与其他关系的一个重要区别是，当遇到不匹配的值时，多对多关系不会自动添加空白行到筛选表中，这可能会导致意想不到的结果。毕竟，一旦出现数据不一致的情况，我们通常可以在 Power BI 模型的可视化对象中看见空白标签，这些空白标签是由空白行引起的。如果没有这些空白，我们无法知晓是否存在数据不一致的行为。\n\n使用前面讨论的筛选表集群是使用常规关系处理事实表中不同粒度的最好方法。\n\n## 2.5内存和性能注意事项\n\nPower BI 模型的设计会极大地影响模型大小，模型大小又与模型的性能高度相关。在本节中，我们将分享一些优化模型性能的最佳实践，作为本章所讨论主题的概括。根据经验，就模型大小而言，较小的模型运行起来更快。Power BI 模型大小可以通过文件大小作来判断；您还可以使用特定的社区驱动的工具（如 DAX Studio）获得更详细的大小和性能的说明。\n\n在设计 Power BI 模型时，请务必遵循以下准则。\n\n•   **列越少越好。**得益于列式数据库的概念，Power BI 模型实现了极高的数据压缩率。但是，它仍然需要记录哪些值是处于同一行中。表中的列越多，模型在查询哪些内容位于何处的工作量就越大。因此，请尽可能地让每个表的列数少一些。\n\n我们观察到很多人的常规操作都是将源表的所有列一股脑全都加载到 Power BI 模型当中，或许是出于方便吧（或者可以说就是因为懒）。请注意，相比于找出那些用不到的列然后删除它们，其实根据需要去添加列或许更容易。模型永远不会有机地缩小，它只会变得越来越臃肿！\n\n•   **选择合适的数据类型。**Power BI 模型内部引擎对存储的数据进行比特级的优化，列式数据库的所有优化都基于此。这意味着任何不是整数的数据类型都必须使用其他方式进行处理，即使用一系列值的字典。当然，这并不是说只有整数这一种数据类型才能有效使用；前文提到过，有几种数据类型的本质也是整数，如日期、定点小数和布尔值。\n\n在模型的关系中同样需要考虑数据类型，因此在建立关系时请尽可能地选择那些使用整数类型的列。\n\n•   **拥有大量的行不是什么要紧的问题，但要注意大量的非重复值。**同样，得益于列式数据库的概念，Power BI 模型可以高效地存储大量的行。它将自动检测在列中存储值的最佳方式，但是，非重复值越多，需要的存储空间就越大。到目前为止，列中唯一值的数量是最值得我们去注意的事情！\n\n通常，节省内存的一种有效方法是删除事实表中的唯一键。许多事务系统为每个事务提供唯一标识符，Power BI 模型在加载这些表时，这些事务是最“昂贵”的列之一。我们遇到过这样的情况：仅仅是从最大的事实表中删除了一个唯一值的列，模型的大小竟然缩小了 90% 以上！\n\n与数据类型一样，非重复值的数量也会对关系产生影响。关系的主键值的数量应保持相对小一些。如果你的模型中有些关系包含着数十万甚至上百万个唯一键值，那么最好你应当马上重构模型。\n\n•   **避免异常值。**在相当多的源系统中，开发人员使用一些特殊值来表示计划的真实数据缺失，或者由于其他什么原因这么做。为了确保不会与真实数据混淆，特殊值通常是异常值（outliers），比如“9999年12月31日”。这些异常值可能会导致 Power BI 模型像面对其他效率较低的数据类型一样使用字典来存储列，即使这些值同样是整数值。这是因为在将值存储为整数时，模型必须考虑列中最小值和最大值之间所有可能的值，在这种情况下，使用字典反而会效率更高一些。\n\n若要避免这种情况发生，请将这些值设置为空白或选择一些接近真实值的特殊值。\n\n•   **你真的需要所有的历史数据吗？**很明显，想要让模型小一些，那么加载的数据量就小一些。我们见过相当多的源系统，保留着很长时间的历史数据。尤其是将数据仓库用作 Power BI 模型的数据源时。您完全可以加载从 2000 年开始往后的销售交易记录，但是你试着灵魂拷问一下自己：都 2022 年了，谁还会去分析 2010 年之前或 2015 年之前的销售数据？对于少数想要从“上古时期”中汲取智慧的“考古学家”来说，最好给他们单独做一个模型。对于日常使用的更丰富、更精细的模型来说，只要包含最近几年的数据即可。\n\n•   **在某些情况下：拆分列。**在某些极端情况下，将一个复杂的列拆分为两个具有更少唯一值的列，可能很有用。组合键（composite key）可能会发生这种情况，例如，由类别代码和序列号组成的产品代码：“A82.019”。单独的类别代码列和序列号的列将分别含有更少的非重复值，并且可以更高效地存储。这种方法在一些更复杂的处理中具有明显的缺点，并且，该组合列很有可能需要被用来建立关系；所以，不到万不得已，尽量还是不要这样做。\n\n当模型的数据量不可避免地与日俱增时，以上所述的许多注意事项也会逐步产生更大的影响。但是，即使使用小的模型，也尽量牢记它们。毕竟，等到问题已经出现了再想着去调整模型，可能就不是那么简单了。\n\n## 总结\n\n在本章中，我们讨论了 Power BI 模型的基本概念。想必您已了解 Power BI 模型与其他数据管理产品的根本不同之处（基于内存的列存储），并深刻体会到由此带来的优势可以实现最佳的模型设计。\n\n优质的 Power BI 模型是由一系列事实表、筛选表以及它们之间的关系构成的高效结构。从数据的粒度、唯一值和值分布的角度仔细甄别，并在此基础上对结构和数据类型做出良好的设计选择，考虑到以上这些，模型的性能一般会比较好。也许更为重要的是，这样精心设计的模型会为后续的一系列 DAX 运算奠定良好的基础。\n\n在下一章中，我们将介绍在 Power BI 模型中使用 DAX 的多种方式。\n\n\n\n------\n\n1 译者注：2029年12月31日是以47,483这个5位的数字来存储，引擎会自动选择数值编码；而9999年12月31日以2,958,465这个7位的数字来存储，引擎会自动选择使用哈希编码。\n\n1 译者注：可以使用笛卡尔积来实现。"
  },
  {
    "path": "_posts/2022-04-10-Power Automate打造的微信聊天记录优质内容存储到notion.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-weixin-notion.jpg\ncatalog: true\ntags:\n    - PowerAutomate\n    - PowerBI\n    - Notion\n\n\n---\n\n\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n比如我们在微信群中看到了一些精彩的回答或者分享，想要集中保存在notion中，就可以多选这些内容，通过邮件的形式发送（不知道为什么视频不能自动播放，可以右键选择新标签页打开播放；或者右键选择显示所有控件）：\n\n<video src=\"https://picgo-1301351990.cos.ap-beijing.myqcloud.com/%E8%A7%86%E9%A2%91/%E5%BE%AE%E4%BF%A1-notion1.mp4\"></video>\n\n\n\n此时，打开notion，20秒左右就会同步到这里，文字和图片、链接都可以实现，图片会慢一些（不知道为什么视频不能自动播放，可以右键选择新标签页打开播放；或者右键选择显示所有控件）：\n\n<video src=\"https://picgo-1301351990.cos.ap-beijing.myqcloud.com/%E8%A7%86%E9%A2%91/%E5%BE%AE%E4%BF%A1-notion2.mp4\"></video>\n\n\n\n\n这一切都是在powerautomate的强大能力下完成的。\n\n\n\n以下为整个流：\n\n![notion-微信](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/notion-%E5%BE%AE%E4%BF%A1.png)\n"
  },
  {
    "path": "_posts/2022-04-10-纯Power Automate打造的Power BI无限刷新-邮箱版-同时刷新多个数据集.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-parefreshpowerbi.jpg\ncatalog: true\ntags:\n    - PowerAutomate\n    - PowerBI\n---\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n![image-20220410142819091](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220410142819091.png)\n\npowerbi的刷新问题一直是很多朋友关注的，多年以来，多种无限刷新的办法提供给大家，像：插件、selenium、python等，但是这些办法有一个大前提，必须电脑开机。\n\n直到使用了powerautomate的出现，打破了这一束缚。现在你无需开机即可实现这一切，甚至这一切根本不需要你自己来布置。你只需要按照要求做就可以了。\n\n\n\n之前的forms填表方式进行无限刷新已经有几十位朋友进行了测试，对于其中反映出来的问题都一一进行了修正。\n\n有朋友反映每次只能提交只能刷新一个报告，能不能一次性提交刷新多个数据集？\n\n而且每次都要登录一个陌生账号才能登录，比较麻烦，能不能提供一个简单的办法？\n\n今天就来解决它！\n\n不过提交办法从forms变为了邮箱提交。\n\n基本过程还是差不多：\n\n首先登录Power BI首页：\n\n国际版**https://app.powerbi.com/home**\n\n世纪互联版**https://app.powerbi.com/home**\n\n鼠标右键点击“检查”，选择**网络**，然后F5刷新页面，点击第一个请求，也就是这个**home**：\n\n![image-20220410142846278](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220410142846278.png)\n\n一般默认会选择**标头**，此时往下滑找到**请求标头**，在里面找到**cookie**，在cookie的值上右键选择复制值。\n\n此时，在电脑的任何位置上新建一个Excel文件，注意是xlsx格式的，打开：\n\n在A1、B1、C1单元格上分别写上cookie、数据集名称和刷新周期：\n\n![image-20220410142903914](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220410142903914.png)\n\n然后将刚才复制的cookie写在A2单元格，如上图。\n\n然后将想要刷新的数据集名称写在B2，如果有多个，就继续往下写。\n\n每一个数据集的C列都填上刷新周期，也即是间隔，单位还是秒。300的意思就是5分钟，同理600代表10分钟。时间间隔一般不要太小，比如30以下，否则会刷新不成功，或者刷新一段时间就停止了。\n\n文件命名可以随意，一般备注数据集的名就行了。\n\n然后，将此文件作为附件发送至邮箱：\n\n**ultimate_refreshes@outlook.com**\n\n主题和内容都可以不写。这是一个自动处理的邮箱。\n\n剩下的操作就交给Power Automate来处理了：（一个整整写了三天，又来来回回改了几十次的flow）\n\n![截图](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%88%AA%E5%9B%BE.png)\n\n\n\n"
  },
  {
    "path": "_posts/2022-04-20-Extreme DAX中文第3章  DAX的用法.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-extremedax.jpg\ncatalog: true\ntags:\n    - Extreme DAX\n---\n\n\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n# 第3章 DAX的用法\n\nPower BI 模型的真正强大之处在于通过使用 DAX 语言进行计算。虽然许多 Power BI 用户专注于模型并试着完全避开使用 DAX，但是除了最简单的基础聚合运算以外，其他所有的计算都需要通过 DAX 来实现。而且，你迟早会在 Power BI 中遇到更复杂的计算需求。根据我们的经验，典型的情况会是：你精心制作的一个 Power BI 报告初稿，会引出有关这些数据的越来越多、越来越复杂的问题。\n\n本书的第二部分旨在为您提供一些启示，让您更好地了解 DAX 可以用来做什么，以及如何使用 DAX 来解决实际业务问题。在我们深入研究第二部分所介绍的场景之前，我们仍然有一些基础知识要介绍。在本章中，我们将简要介绍 DAX 在 Power BI 中的不同用法。\n\n•   计算列\n\n•   计算表\n\n•   度量值\n\n•   安全筛选器\n\n•   DAX 查询\n\n除此之外，我们还将讨论如何使用 DAX 创建日期表。本章最后总结了一些使用 DAX 的最佳实践。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image002.jpg) | 本章附带一个带有示例的 PBIX 文件。如需下载请关注公众号：PowerBI生命管理大师学谦，回复关键字“dax”。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n## 3.1 计算列\n\n**计算列**（calculated column）是通过执行 DAX 计算，在 Power BI 模型的表中新建一个数据列。举个简单的例子：通过将销售的产品数（Quantity）乘以每个产品的价格（Price）来计算销售额（Amount）。注意，在 DAX 中列名要写在方括号内。\n\nAmount = [Quantity] * [Price]\n\n如图 3.1 是生成的计算列。\n\n![表格  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image004.png)\n\n图3.1 计算列\n\n在搭建 Power BI 模型的过程中，新建一些计算列是添砖加瓦的最简单直接的办法。尤其是当您经常使用 Excel，这个操作会非常自然且顺手，因为大多数 Excel 用户借助 Excel 工作时都是直接在列中写公式进行计算。但我们强烈建议：**尽量不要**使用计算列，除非你有什么万不得已的理由。如下给出解释。\n\n•   计算列创建的新数据会占用模型中的空间。上一章我们讨论过，列越多，模型就越大而且速度越慢。\n\n•   如果出于某种原因，你需要从模型中删除一张表并以另一种方式再次创建这个表（你可能不相信自己会这么傻，但是请放心，总会有这么一天的），所有的计算列都会随着你的删除动作一并消失，然后，从头再来。\n\n•   用于创建计算列的列（比如上一个示例中的 [Quantity] 和 [Price] 列）需要保留在模型中，但这一列可能并没有其他的用途。在此示例中，你可以好好想一下 [Price] 列还能用来做什么。或许，可以计算每个产品的平均价格？答案是否定的：平均价格应按销售的产品数量加权，因此 [Price] 列的直接平均值是不正确的。正确的做法是：将总销售额除以销售的产品总数，并且计算过程根本用不到 [Price] 列。\n\n•   计算列中的计算结果是静态的：仅在创建列或者刷新 Power BI 模型时这些值才会被计算。这与 DAX 和 Power BI 报表的动态特性相悖。\n\n计算列的问题在于，大多数情况下，这些操作属于数据准备层面，或者属于我们在第1章“商业智能中的 DAX”中讨论的五层模型中的“数据预处理”层。在进行数据预处理这一道工序时，有很多比 DAX 更好的工具，比如 Power Query。最佳模型是销售交易记录表中保留 [Quantity] 和 [Amount] 这两列，删除 [Price] 列。\n\n当然，不使用计算列这一规则也有一些例外，当您使用 DAX 处理更高级的方案时，可能会遇到这些例外情况。\n\n•   有时，在创建复杂的 DAX 计算时，您会发现其中一部分实际上是固定不变的，基于此，它确实可以用计算列来实现。如果这是一个复杂的计算，而且在报告的使用过程当中需要反复地计算这个结果，那么通过计算列来实现，您可以获得显著的性能提升。不过，您还是应该先考虑在“数据预处理”层中创建列！\n\n•   有一些计算被用来生成模型中的列，如果这些计算使用 Power Query 这样的“数据预处理”层中的工具很难实现，而使用恰当的 DAX 函数却可以很简单地实现。在这种情况下，通过使用计算列，不仅可以节省开发时间，而且数据刷新的性能也会大大提高。这种情况一般发生在所需列的值是某些复杂聚合的结果时。但是，如果您遇到这种情况，还是应该先深入思考一下，假设没有计算列，这个问题能否得到解决！\n\n总而言之，除非有很好的理由，否则还是不要使用计算列。\n\n## 3.2 计算表\n\n计算表（Calculated table）与计算列相当：它们将数据直接添加到 Power BI 模型中，只不过是以整个表的形式。若要创建计算表，通常需要特殊的 DAX 表函数。在第4章“上下文和筛选”中我们将简单介绍一些表函数，并且在本书第二部分，我们将一起深入学习这些 DAX 表函数；。\n\n若要在 Power BI 模型中创建一个简单的计算表，可以使用表构造函数。如下的表达式仅由大括号之间的值列表组成，它创建一个包含一列的表。\n\n```\nExample = {1,2,3}\n```\n\n此公式的结果是一个名为 Example 的表，只包含一个 [Value] 列，如图3.2所示。\n\n![img](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image006.png)\n\n图3.2 使用表构造函数创建的计算表\n\n请注意，表构造函数不会对创建的表提供太多的设定。生成的列名为 Value，并且 Value 列的数据类型与所提供的值一致（很明显，在大多数时候是相当准确的）。如果提供的值是不同类型的数据，那么它将自动选择一个可以存储所有值的数据类型。例如：\n\n```\nExample2 = {1, 2, \"3\"}\n```\n\n此公式生成的表中 Value 列是文本数据类型。\n\n表构造函数允许创建具有多个列的表，方法是按行提供一系列值的列表，每一行用括号分隔，代码如下。\n\n```\nExample3 = {(1, \"Red\"), (2, \"Green\"), (3, \"Blue\")}\n```\n\n如图3.3所示是以上公式的结果呈现。\n\n![图形用户界面, 文本, 应用程序  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image008.png)\n\n图3.3 具有两列的表构造函数\n\n我们也可以使用 DATATABLE 函数来创建表，这样的表可以自定义列名并且对数据类型也可以严格把控。该函数的参数是一系列列名和数据类型对，以及包含表中每一行的值的列表。DATATABLE 函数有两个奇怪的特性：首先，数据类型的名称与 Power BI 模型中使用的数据类型的名称不同（比如：INTEGER表示整数类型，STRING表示文本类型等），并且，一行中的值必须包含在大括号中，而不是像表构造函数中那样用小括号分隔。图3.3 中的表，如果使用 DATATABLE 来创建，可以得到更好的列名显示，公式如下。\n\n```\nExample4 = \n\nDATATABLE(\n\n  \"Number\", INTEGER,\n\n  \"Color\", STRING,\n\n  {\n\n    {1, \"Red\"},\n\n    {2, \"Green\"},\n\n    {3, \"Blue\"}\n\n  }\n\n)\n```\n\n计算表通常用于在 Power BI 模型中创建日期表或日历表。本章稍后会对这些内容展开讨论。\n\n计算列的一些问题同样也适用于计算表：计算表会增加 Power BI 模型的大小，并且你可能正在执行一些实际上是数据准备层面的工作。但是，与计算列相反，计算表不会与模型的其他元素紧密耦合。当你删除那些与计算表相关的用于计算的列或表时，您将收到错误提示；但是只要再次添加这些表或列，这些错误也就没了。\n\n通常，我们的建议是，如果您想要的表可以在五层模型的“数据预处理”层中得到，则不要使用计算表。当您无权参与“数据预处理”层面的工作时（比如，使用集中管理的数据仓库），因为有些表数据仓库并没有提供，或者数据仓库中根本无法储存这样的表，那么此时就可以使用计算表。\n\n## 3.3 度量值\n\n度量值（Measures），或在某些早期模型版本中叫做计算字段（calculated fields），无疑是 Power BI 模型中最强大的元素，没有之一。实际上，我们在 Power BI 模型上执行的大部分工作都可以归结为设计并应用 DAX 度量值。\n\n在 Power BI 报表中使用事实表中的数字列时，列的值将被执行聚合运算。常见的基本聚合运算包括：求和（sum）、平均值（average）、最小值（minimum）、最大值（maximum）、计数（count）、非重复计数（distinct count）以及一些统计聚合，如标准差（standard deviation）、方差（variance）和中位数（median）。基本聚合运算的种类因数据类型而异，比如，对于日期列，只能选择“最早”、“最新”、“计数”和“非重复计数”这些聚合运算，而不能是其他的。以这种方式使用列时，Power BI 模型会在后台自动创建一个隐式度量值（implicit measure）：隐式度量值是一个聚合函数，能够根据选择的方式对列中的值进行聚合运算。\n\n在实际业务场景中，大部分所需的见解都需要通过复杂的聚合运算来实现，基本的聚合运算完全无法满足要求。相当一部分人（包括 Excel 用户和数据仓库开发人员）会通过生成（计算）列，来创建能提供所需结果的数据，同时还得保证只需要一个基本聚合就能实现。例如，在 Excel 模型和数据仓库中，您可能会遇到一个指示器（indicator），该指示器确定某一行数据是否属于“当前年初至今”。同样，这是一个静态解决方案，不会让您得到两个月前的年初至今数据。\n\n因此，度量值隆重登场了。DAX 允许你通过编写公式创建显式度量值（explicit measure）来实现自己的个性化聚合。例如，前面的计算列部分中讨论的加权平均价格可以通过 DAX 度量值来实现，公式如下。\n\n```\nAverage Price = SUM(fSales[Amount]) / SUM(fSales[Quantity])\n```\n\n在此公式中，假定具有销售交易记录的表叫做 fSales 表。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image009.jpg) | 除法运算符 “/”通常由另一个 DAX 函数  DIVIDE 替换，代码如下。  Average Price =   DIVIDE(    SUM(fSales[Amount]),    SUM(fSales[Quantity])  )  DIVIDE的优点是当0作为除数时的处理方式比较优雅[[1\\]](#_ftn1)。这是 DAX 度量值的额外优势的一个简单示例，使用适当的 DAX 函数而不是对列进行基本聚合。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\nDAX 度量值应当作为您为 Power BI 模型提升智能水平的默认选项。度量值不会向模型添加数据，因此可以使模型一直保持精简与快捷。但是，由于计算是在用户查看报表时按需完成的，因此必须努力创建最高效的计算方式。在本书的第二部分，我们不仅关注如何使用 DAX 度量值解决业务方案，还将重点阐述如何创建高效的 DAX 度量值。\n\n对于平时经常使用 DAX（尤其是 DAX 度量值）的人来说，有一些基本的概念需要透彻的理解。其中包括 DAX 上下文、通过上下文转换进行 DAX 筛选以及 DAX 表函数的概念。我们将在第4章 “上下文与筛选”中详细讨论这些概念。\n\n## 3.4 DAX安全筛选器\n\nDAX 还可用于在 Power BI 模型中实现安全性。当用户检索报表时，他们将能够通过该报表查看模型提供的所有结果。在许多情况下，需要根据其角色或身份限制用户看到的内容。例如，请看下面的 DAX 安全表达式。\n\n```\nCustomer[Region] = \"Europe\"\n```\n\n为特定安全角色设置时，此 DAX 安全筛选器将使该角色中的用户只能查看欧洲区域中的客户以及与这些客户相关的数据。\n\n我们将在第5章进一步介绍使用 DAX 实现的安全性。\n\n## 3.5 DAX查询\n\n使用 DAX 的最后一种方法是用作查询语言。当你使用 Power BI 可视化报表时不需要用到此功能，但面向关系型数据库的经典报表工具主要依赖于从数据库中检索自定义数据集来呈现报表。这些的常见数据源是数据仓库或其他数据库；但发布 Power BI 数据集形式的 Power BI 模型也可以以这种方式使用。请注意，截止到本书编写时，你需要具有 Power BI 高级版（Premium）许可证才能执行此操作，每容量（per capacity）或每用户（per user）都可以。\n\nDAX 查询的一个特定用例是在 Power BI 分页报表中。分页报表是使用 Power BI 报表生成器（Power BI Report Builder）开发的，并且可以连接到已发布的 Power BI 模型。其他所有的用例都是在 Power BI Desktop 中开发。\n\n如图3.4所示，Power BI 报表生成器与 Power BI 模型建立连接时，需要填写 DAX 查询以从中检索数据。\n\n![图形用户界面, 应用程序  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image011.jpg)\n\n图3.4 在 Power BI 报表生成器中编写 DAX 查询\n\n在 Excel 中使用 Power Pivot，作为默认的数据透视表输出的替代方法，可以使用 DAX 查询从 Power Pivot 模型中检索数据，。\n\n与计算表一样，DAX 查询需要表表达式。在本例中，函数 EVALUATE 用于计算表表达式并返回表。下面的表达式返回完整的 Customer 表：\n\n```\nEVALUATE( Customer )\n```\n\n在表表达式中，可以使用所有的 DAX 函数，包括可用于从模型中检索特定聚合结果的 DAX 度量值。唯一的限制是：表达式的最终结果一定是表。\n\n## 3.6 日期表\n\n几乎所有的 Power BI 模型都包含与日期相关的数据。因此，**日期表**（或日历表，或者你喜欢怎么称呼它都可以）是 Power BI 模型中的很常见的组成部分。由于 DAX 时间智能函数的存在，日期表在模型中具有特殊的地位（有关于这些函数的详细信息，请查看第 4 章“上下文和筛选”）。\n\n日期表必须包含要分析的日期区间中的所有日期，并且每个日期占用一行。建议以模型中的最小年份作为日期表的开端，并以最大年份结束[[2\\]](#_ftn2)。日期表必须具有日期列，该列是日期表的唯一键（您也可以自己设置此列的名称）。表中的其他列是每天的属性，如年、月、季度、工作日等。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image012.jpg) | Power BI 有一个叫做“自动日期/时间”的特性，启用该功能后，将为模型中具有日期或日期/时间数据类型的每一列创建一个隐藏的日期表，并辅以年/月层次结构。  如果您尚未执行此操作，请立即关闭此功能！因为这会导致臃肿的模型并严重影响模型的性能。这些隐藏的日期表，仅仅对那些只关注建模而不想有任何其他操作的用户来说很方便，但对于任何有经验的用户来说并没有什么价值。当然，在  Power BI 报表中，想要通过选择特定的日期范围（比如一年）而得到固定准确的结果，仍然需要一个合适的日期表。稍后我们就将介绍如何创建日期表。  强烈建议，在 Power BI Desktop 的选项中，关闭“新文件的自动日期/时间”选项，以永久避免产生这些表。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n可以使用标记为日期表选项标记包含日期的表。这样，包含日期的列将被认定为正式的日期列：\n\n![图形用户界面, 文本, 应用程序  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image014.jpg)\n\n图3.5 将表标记为日期表\n\n在第 4 章中，我们将讨论时间智能函数，届时，您将了解将一个表标记为日期表有什么优势。\n\n### 3.6.1 创建日期表\n\n从技术上讲，日期表与其他表没有什么不同。您可能在某个地方有可用的日历数据，那么此时，只需将日期导入 Power BI 模型即可。当然，还有一些其他方法，比如在 Power Query 中的通过输入一些参数（例如，表应跨越哪些年份）来创建日期表，不过本书并不打算对此展开讨论。\n\n在本书中，我们将重点介绍如何使用 DAX 公式通过计算表的方式创建一个日期表。有两个 DAX 函数专门用于执行此操作：CALENDAR 和 CALENDARAUTO。这两个函数都返回一个包含日期的单列表。\n\nCALENDARAUTO 函数将搜索整个模型，并从数据类型为“日期”\"或“日期时间”的所有列（不包括计算列和计算表中的列）中查找最小日期和最大日期。日期范围从找到的最小日期所在年份的第一天开始，一直持续到最大日期所在年份的最后一天。虽然这听起来很方便，但您必须意识到，当模型包含诸如出生日期或像2199年12月31日这样奇怪的异常值时，它将创建一个跨越数十年甚至几百年的巨大表格。\n\n因此，更好的选择是 CALENDAR。该函数有两个参数，即要创建的日期表的第一天和最后一天，代码如下。\n\n```\nCALENDAR(\n\n  DATE(2021, 1, 1),\n\n  DATE(2023, 12, 31)\n\n)\n```\n\n由于该函数生成的结果是单个 Date 列，因此想要得到一个合适的日期表还需要添加更多其他的列。这可以通过使用计算列来完成，但是您可以通过使用 ADDCOLUMNS 函数在一个公式中就得到所有的列，代码如下。\n\n```\nDate = \n\nADDCOLUMNS(\n\n  CALENDAR(\n\n    DATE(2021, 1, 1),\n\n    DATE(2023, 12, 31)\n\n  ),\n\n  \"Year\", YEAR([Date]),\n\n  \"Month\", FORMAT([Date], \"mmmm\", \"En-US\"),\n\n  \"MonthNr\", MONTH([Date]),\n\n  \"Year/Month\", FORMAT([Date], \"yyyy-mm\")\n\n)\n```\n\n在上面的公式中，ADDCOLUMNS 函数获取 CALENDAR 函数的结果并向其添加列。您必须为添加的每一列提供一个名称，同时还得有一个提供相应值的表达式。该公式提供了一个使用 FORMAT 函数的范例，该函数可用于应用基于某些值的各种格式，在本例中为日期值，同时还可以自定义设置区域格式。\n\n以上公式的结果如图3.6所示。\n\n![表格  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image016.png)\n\n图3.6 使用 DAX 公式创建的日期表\n\n在实际模型中，为了更好地匹配新数据的加载，日期表的开始日期和结束日期一般而言是需要设置为动态的。例如，您可以使用 MAX(fSales[OrderDate]) 在 fSales 表中查找最新日期，并将该值用作日期表的结束日期。您还可以使用 DAX 在事实表中查找最后一个订单日期年份的最后一天。我们就不详细展开说明了。\n\n## 3.7 使用 DAX 的最佳实践\n\n使用 DAX 时，您将受益于以下一些最佳实践。应用这些方法将有助于你创建轻便快捷的模型，长远来看能够让你更轻松地维护模型，并帮助你更好地支持那些从你模型中创建 Power BI 报表或其他输出的人。\n\n### 3.7.1 首先考虑使用 DAX 度量值\n\n如果在上文中没有足够地表达清楚，那么容我再重复一边：您的主要 DAX 工具应该是度量值。度量值是高度动态的，它们不会使模型变得更臃肿，并且没有哪个计算不能通过度量值来实现。\n\n根据经验，除非你找到了足够好的理由，否则，尽量别碰计算列和计算表！\n\n### 3.7.2 使用显式度量值\n\n我们建议创建显式 DAX 度量值，而不是直接在可视化报表中使用（事实）表中的数字列，原因如下。\n\n•   在报表中使用列时，Power BI 模型无论如何都会创建度量值，并且很容易自己执行此操作。\n\n•   显式度量值可以指定明确的名称，例如总销售额（Total sales）而不是总和（Amount），也不是 Excel 的 Power Pivot 中的汇总（Sum of Amount）。\n\n•   还可以为度量值指定输出格式。例如，可以设定 Total sales 度量值的显示不带货币符号和小数但使用千位分隔符。此格式可以与从数据类型派生的格式不同。\n\n•   显式度量值可用作更复杂计算的构建基块（见下文）。隐式度量值要么无法使用，要么不方便使用，因为它们无法更改。\n\n不直接使用事实表中的数字列还有其他的优点，即不会有使用不正确聚合的风险。就像我们之前讨论的平均价格度量值那样，如果只是向视觉对象添加列，就很容易出错。\n\n### 3.7.3 使用基本度量构建代码块\n\n在 DAX 公式中，度量值可以调用，以便在计算中使用这些度量值的结果。使用基本度量值（事实表中最简单的数字列的聚合）作为代码块来构建 DAX 度量值有助于逐步创建一系列更复杂的计算。\n\n使用基本度量值使您不必一遍又一遍地考虑如何计算基础的结果。我们看到很多人这样做。此外，基本度量值可以让您轻松地调整业务逻辑。通常情况下，在开发 Power BI 解决方案的后期阶段，总是会有一些额外的业务逻辑出现。举个例子，起初您可能会被告知“销售额是所有发票金额的总和”。但是，当你费尽千辛万苦终于做出了一个比较满意的 Power BI 模型时，你如果听到“哦，等等，*X* 类型的发票不计入销售额，应将其排除在外”时，你会不会崩溃呢？您需要将所有与销售相关的度量值全部重做一遍，以将 X 剔除在外。但是如果你使用基本度量值，您只需在基本度量值的公式中排除类型 X 发票即可。 \n\n### 3.7.4 隐藏模型元素\n\n在设计 Power BI 模型时你可能会认为创建报表的人也只有你一个。但是实际上，其他人可能会基于您的模型来构建自己的报表。对于你们所有人来说，最好隐藏模型中会遮盖有用表、列和度量值的元素。\n\n•   关系中的外键列应当隐藏：主键上相同的值，并且会正确地筛选关系的另一端。\n\n•   不在报告中展示的技术（键）列应当隐藏。\n\n•   我们建议隐藏事实表：所有的外键列都应当隐藏，数据格式为数字的列不应直接使用，而应通过显式度量来使用。除此以外，您的事实数据表中可能还有其他列，我们建议您考虑将它们移动到适当的筛选表或者完全删除它们。（事实表中的某些列可能仅用于筛选，而不会向用户公开；它们可以保留在事实表中。）\n\n•   在书写复杂 DAX 度量值时，往往会有一些进行中间计算的 DAX 度量值，他们应当隐藏起来。\n\n有策略地隐藏 Power BI 模型中的一些元素将会避免部分混淆，并减少作为模型设计者的你因“模型不起作用”而收到的问题数。\n\n### 3.7.5不要将数据和度量值混在一起——请改用度量值表\n\nDAX 度量值始终具有主表，该表是向模型设计人员显示度量值位置的表。更重要的是，在创建 Power BI 报表时，报表设计人员可以在模型的“字段”（Fields）窗格中看到度量值。我们观察到许多人将度量值放在包含所要聚合的列的事实表中。虽然这对于简单明了的度量值（如基本聚合）是可行的，但我们建议不要这样做，原因如下。\n\n•   更复杂的度量值将聚合来自不同表中的列，此时无论将哪个表作为主表都将产生歧义。\n\n•   更重要的是，与计算列一样，如果需要删除一个表并重新创建这个表，您将丢失该表下的所有度量值。\n\n我们建议将所有度量值存储在一个或多个专用的度量值表中。这些表不包含数据，而只用来存放度量值。虽然我们说过最好不要创建计算表，但是度量值表是一个例外。创建度量值表的最简单方法是使用以下公式创建一个计算表。\n\n```\nResults = ROW(\"ZZ\", \"OK\")\n```\n\n这将创建一个名为 Results 的表，其中包含一列 ZZ 和一行数据。该单行的 ZZ 列中的值是文本“OK”。这个单独的一列必须得存在，因为连一列数据都没有的表并不是表；但是当你隐藏该列时，Power BI 会自动将其识别为度量值表，并将其放置在“字段”窗格的顶部。这使得度量值很容易被找到。该列命名为 ZZ，这样它就会处在度量值表的最底部，而不是一堆度量值中间。此列及其中的值永远不会被使用，因此您可以将“OK”替换为您喜欢的任何内容。结果如图3.7所示。\n\n![图形用户界面, 应用程序  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image018.jpg)\n\n图 3.7 Power BI Desktop 的数据视图（左）和报表视图（右）中的度量值表\n\n您也可以在 Power Query 中创建度量值表，例如，通过“输入数据”选项。其工作方式也相同：隐藏数据列并添加度量值以使表移动到“字段”窗格的顶部。\n\n但是，在撰写本书时，用于度量表的图标略有不同：通过 Power Query 导入表时，将使用特定的度量表图标，但将其创建为计算表时，将使用计算表的通用图标，如图3.8所示。\n\n![图形用户界面, 应用程序, 电子邮件  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image020.jpg)\n\n图3.8 计算表生成的度量值表（顶部）和导入生成的度量值表（底部）\n\n对于复杂模型，可以在模型视图中使用“显示文件夹”对度量值进行分组。您甚至可以决定使用多个度量值表。例如，我们有时会对所有基本度量值使用单独的度量值表，这些度量值仅用作更高级别计算的构建基块。通过执行此操作，可以一次性隐藏或显示所有的构建基块度量值，只需手动隐藏 ZZ 列。\n\n### 3.7.6 表类型\n\n建议您明确区分我们在本章和上一章中讨论过的表的类型。除了已经讨论过的三种类型之外，还有另一种表类型，即：*辅助表*。\n\n•   事实表包含要聚合的主要数据，但不在报表中使用其中的列，处于隐藏状态。\n\n•   筛选表（或维度表）包含要筛选模型结果的所有属性。\n\n•   度量值表不包含任何数据，只包含 DAX 度量值，位于字段列表的顶部。\n\n•   辅助表是用于驱动特定报告行为（如报告时间段的选择）的小表。您将在第六章 “动态可视化”中了解有关辅助表的更多信息。\n\n您无需通过为这些表设置特定的名称来区分这些表的类型。例如，我们并不喜欢在筛选表名称中添加 dim 前缀，比如 dimCustomer、dimCalendar 等。这对使用您模型的用户来说并不是很友好，他们可能会琢磨 dim 到底意味着什么。你应该尽可能地让 Power BI 模型的元素为自己代言，用户一眼就能看出来那是用来干什么的。隐藏事实表、使用度量值表并为筛选表提供描述性名称会呈现一个比较好的结果，即“字段”窗格在顶部展示可用的（计算的）结果，也就是度量值，在底部罗列那些用于筛选这些结果的所有属性，它们都很有条理地成组展示（作为模型设计者，您知道这些属性就是表）。\n\n## 总结\n\n在本章中，你已了解 DAX 在 Power BI 模型中的不同用法：计算列、计算表、度量值、安全规则和查询。主要结论是，DAX 度量值是（或者说应该是）从模型生成有价值结果的主要方式，实际上，本书的其余部分我们将重点介绍 DAX 度量值。我们为您提供了一些使用 DAX 的最佳做法：避免使用计算列，使用显式 DAX 度量值，创建简单的 DAX 度量值并将其用作更高级计算的构建基块，使用度量值表，以及隐藏可能使报表设计者感到困惑的模型元素（即使这是您自己）。\n\n下一章将介绍的可能是使用 DAX 时要理解的最重要的概念：上下文和筛选。之后，我们将整装待发，一起去探索第二部分的高级 DAX 业务案例。\n\n\n\n------\n\n1 译者注：0作为除数时，如果使用“/”，得到的结果是“∞”，而使用DIVIDE函数会显示空白。\n\n2 译者注：例如，假设事实表中包含的最小日期和最大日期分别为2019年2月14日和2022年5月20日，则该日期表的日期范围最好设定为2019年1月1日至2022年12月31日。"
  },
  {
    "path": "_posts/2022-05-06-这个网站用PowerBI、PowerQuery不好爬？这一招交给你.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-parefreshpowerbi.jpg\ncatalog: true\ntags:\n    - PowerBI\n    - PowerQuery\n---\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n\n\n有同学想用powerbi爬这个网址\n\nhttps://flk.npc.gov.cn/fl.html\n\n但是发现它跟其他网址不太一样，因为翻页的时候地址栏还是一样的地址。\n\n遇到这种情况该怎么办呢？\n\n今天教你一招来搞定，此方法适用于很多网站，并且也是一项网爬的基本技能。\n\n\n\n# 一、获取真正的url链接\n\n## 1、打开网页，右键空白处-检查，选择网络：\n\n![image-20220506185954314](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506185954314.png)\n\n## 2、点击翻页，下方会出现一个新的链接：\n\n![image-20220506190114051](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506190114051.png)\n\n\n\n## 3、点击链接，右方默认会出现如图所示的栏目，选择标头，复制下方的请求URL，记住方法为GET：\n\n![image-20220506190208537](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506190208537.png)\n\n\n\n## 4、分析URL\n\nhttps://flk.npc.gov.cn/api/?page=5&type=flfg&searchType=title;accurate&sortTr=f_bbrq_s;desc&gbrqStart=&gbrqEnd=&sxrqStart=&sxrqEnd=&sort=true&size=10&_=1651834715885\n\npage=5代表第5页，size=10代表一页有10行内容，最后的那个& =之后内容是个时间戳，一般无所谓的，删掉即可，其他内容代表一些筛选项\n\n所以链接可以简化为：\n\nhttps://flk.npc.gov.cn/api/?page=5&type=flfg&searchType=title;accurate&sortTr=f_bbrq_s;desc&gbrqStart=&gbrqEnd=&sxrqStart=&sxrqEnd=&sort=true\n\n## 5、将简化的链接输入地址栏\n\n返回了10条记录\n\n![image-20220506194615862](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506194615862.png)\n\n\n\n并且我们在这里发现，该筛选一共有613条记录，每页10条，也就是62页：\n\n![image-20220506194712655](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506194712655.png)\n\n我们可以将上方链接替换为62看看，果然这一页上只有2条记录：\n\n![image-20220506194854360](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506194854360.png)\n\n\n\n# 二、PowerBI或PowerQuery获取数据\n\n## 1、创建一个文本参数\n\n![image-20220506200735671](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506200735671.png)\n\n## 2、新建源-web：\n\n![image-20220506200122111](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506200122111.png)\n\n将此参数替换掉链接中的那个数字5：\n\n![image-20220506200709607](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506200709607.png)\n\n\n\n## 3、展开得到数据：\n\n![image-20220506200913015](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506200913015.png)\n\n\n\n## 4、创建参数：\n\n在刚刚得到的这个表上右键创建函数：\n\n![image-20220506201100783](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506201100783.png)\n\n\n\n## 5、新建一个空查询：\n\n![image-20220506201153247](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506201153247.png)\n\n\n\n写入= {1..70}获取一个列表，并转换为表：\n\n一共是62页数据，为了预备以后更新，设置为70页\n\n![image-20220506201345944](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506201345944.png)\n\n\n\n## 6、调用自定义函数：\n\n\n\n![image-20220506201442175](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506201442175.png)\n\n每一列都生成了一个table：\n\n![image-20220506201513375](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506201513375.png)\n\n## 7、展开table：\n\n![image-20220506201550047](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506201550047.png)\n\n## 8、关闭并上载即可得到想要的数据：\n\n![image-20220506195604707](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506195604707.png)\n\n\n\nok！搞定！\n\n你学会了吗，以后遇到这种翻页时地址不变的网页，就可以采用相同的办法解决！\n\n# 推广：\n\n想要获取更多PowerBI的精彩内容分享以及全年365天不限次提问，以及公众号文章的示例文件，请加入学谦PowerBI知识星球，现在更有20元优惠券可以领取，数量有限，欲购从速！\n\n![海报 (3)](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%B5%B7%E6%8A%A5%20(3).png)\n\n\n\n\n\n另外，学谦的PowerAautomate知识星球全新上线，目前已集结40+小伙伴，测试阶段50个白菜价名额马上就用完，机不可失，失去了永远不再来！\n\n![image-20220506202008626](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220506202008626.png)\n"
  },
  {
    "path": "_posts/2022-05-10-Extreme DAX中文第4章  上下文和筛选.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-extremedax.jpg\ncatalog: true\ntags:\n    - Extreme DAX\n---\n\n\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n# 第4章 上下文和筛选\n\n编写 DAX 公式时要掌握的核心概念是**上下文**。\n\nDAX 作为一门动态数据分析语言，与 Excel 函数、SQL 查询 和 Power Query 脚本有着根本不同的原因就在于上下文的概念。以上所述的所有其他语言的公式只会在数据发生变化时才会返回不同的结果（除了一些例外情况，例如使用参数时），但是单个 DAX 公式就可以同时提供多个不同的结果，具体取决于您使用它的位置和方式，也就是：上下文。\n\n同时，上下文也是使用 DAX 实现一些高级应用的关键。当你跨过了经常犯一些低级错误的菜鸟阶段（如不知道要使用哪些 DAX函数、语法不正确或忘记括号等）之后，你在使用 DAX 时可能要天天和上下文打交道。\n\n我们甚至会说：**DAX 中的每一个问题都来自于上下文，并且所有问题的解决方案都是通过仔细审视上下文找到的。**\n\n这种说法很少会被否定！\n\n在本章中，我们将讨论一些有关上下文的基本主题，这些主题是理解本书第二部分所有内容的必要条件。本章主要涵盖如下内容。\n\n•   DAX 上下文简介\n\n•   DAX 筛选：使用 CALCULATE 函数\n\n•   时间智能函数\n\n•   改变关系的行为\n\n•   DAX 中的表函数\n\n•   使用表函数进行筛选\n\n•   DAX 变量\n\n由于本书第二部分的各个章节会深入演示 DAX 上下文的各个方面，因此本章将相对较少地展现示例。\n\n## 4.1 Power BI 模型\n\n本章中的示例取自一个简单的 Power BI 模型。该模型由一个事实数据表 fSales 和一些筛选表组成：\n\n![image-20220510133020925](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510133020925.png)\n\n图4.1 示例 Power BI 模型\n\n本章的模型文件 1.4 上下文和筛选 pbix 。如需下载请关注公众号：PowerBI生命管理大师学谦，回复关键字“dax”。\n\n## 4.2 DAX 上下文介绍\n\nDAX 上下文的通用术语是**计值上下文**（evaluation context）：DAX 公式在上下文中计算，从而得到特定的结果。我们将上下文分为以下三种类型。\n\n•   行上下文\n\n•   查询上下文\n\n•   筛选上下文\n\n在大多数 Power BI 文档和出版物中，只涉及两种类型的上下文：行上下文和筛选上下文。查询上下文这一术语在 Power BI 问世之前就已经出现在 Excel 里的 Power Pivot 中了（没错，那会我们就开始在使用了），并且我们一直在使用它。根据我们在 DAX 课程授课中的经验，区分查询上下文和筛选上下文有助于大家理解更复杂的应用场景。\n\n以下展开讨论每一种上下文类型。\n\n### 4.2.1 行上下文\n\n行上下文是创建计算列时会用到的上下文类型。定义计算列的 DAX 公式在表中的每一行分别计算一次。计算结果通常特定于对应的行。原因是，同一表中其他列中的值被用在计算中，而这些值在每行中一般是不同的。例如，在 fSales 表（销售表）中创建一个用于计算 SalesAmount（销售额）和 Costs（成本）之间差额的计算列，定义为 Margin（利润），代码如下。\n\nMargin = fSales[SalesAmount] – fSales[Costs]\n\n由于直接引用了列，您可能立即意识到这个公式用于计算列。此类计算只能在行上下文中完成，这是行上下文与其他上下文类型的主要区别。（在简单的计算列公式中，fSales 这样的表前缀通常被省略。）\n\n注意，在计算列中直接对某些列进行引用时，只能对当前计算所在的行上的列值起作用，如果要从其他行中检索值，您需要采用完全不同的方法。这与 Excel 中的计算完全不同。在 Excel 中，从“上面的行”中获取一个值是很常见的。当你意识到 Power BI 模型表中的行之间没有严格的顺序时，就很容易理解这个问题了。\n\n只有少数 DAX 函数专门用于在行上下文中工作。如果包含计算列的表与另一个表相关，则在每行中，可以使用 RELATED 函数从另一个表中的列中检索相应的值。下面的公式利用 fSales 表中的 OrderDate 列（订单日期列） 和 Date 表（日期表）中的 Date 列（日期列）之间的关系来检索每行所对应的年份。\n\n```\nYear = RELATED('Date'[Year])\n```\n\n图4.2展示了以上公式的计算结果： fSales 表中的 Year（年份）列。\n\n![表格  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image004.jpg)\n\n图4.2 添加 Year 计算列（为便于阅读，删除了部分列）\n\n在使用 RELATED 函数时要注意一个限制条件：关系的另一端必须是“一”端，也就是说，另一个表（在此示例中为 Date 表）中的相应的列必须具有唯一值。毕竟，公式的结果需要产生单个值。\n\n当关系的基数反转时，可以使用 RELATEDTABLE 函数。例如，要向 Date 表中添加一个计算列，其中包含每天的销售交易记录数，则下面的公式可以实现。\n\n```\nNumber of Transactions = COUNTROWS(RELATEDTABLE(fSales))\n```\n\n对于 Date 表中的每一行，RELATEDTABLE 函数都会生成与之相关的fSales表中的一系列行的集合。由于生成的结果是一个表，不能直接用作计算列中的值，因此我们使用了 COUNTROWS 来简单地计算该表中的行数。\n\n尽管 RELATEDTABLE 专门用于行上下文，但它与 RELATED 的根本不同之处在于它在背后会使用不同的上下文类型。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image006.jpg) | 如第 3 章“DAX的用法”中所述，我们不鼓励使用计算列。这并不意味着您不必处理行上下文。行上下文在 DAX 表函数中也起着重要作用。本章稍后将对此进行详细介绍。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\nOK！让我们继续其他的上下文类型学习之旅。在我们讨论了查询上下文和筛选上下文之后，就可以清楚地阐述行上下文的一些特殊性了。\n\n### 4.2.2 查询上下文\n\n在使用 DAX 度量值时我们会用到查询上下文。与之前的行上下文类似，查询上下文使得 DAX 度量值返回特定的结果。当然，不同之处在于，我们不是在单个表的内部展开工作。简而言之，查询上下文是指在 Power BI 模型中选择的行的集合，基于这个集合进行 DAX 公式的计算。恰当地区分查询上下文中两个密切相关但独立的元素是很有必要的。\n\n•   **选定内容**（selection）是指模型中各个表中在特定上下文中选择的行的集合。\n\n•   **筛选器**（Filters）是导致选择行的原因。\n\n在查询上下文中，筛选器来自于 Power BI 报表中的元素。它们有多种类型：切片器、筛选器窗格中的筛选器、视觉对象中的标签或其他视觉对象中的选定项。以上所述任何一个都会在列上形成特定的规则；例如，在图4.3中，切片器在 Year 列上引发筛选器：年份等于2019。不同列上可以有许多筛选器，甚至同一列上也可能有多个筛选器。所有这些筛选器共同确定在每个表中选择哪些行：同时满足每个筛选规则的所有行。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image006.jpg) | 第 6 章 “自动存在”中对视觉对象筛选器进行了全面的讨论。 |\n| ------------------------------------------------------------ | ------------------------------------------------------ |\n\n \n\n![图表, 条形图  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image008.jpg)\n\n图4.3 一个简单的 Power BI 报表\n\n在查询上下文中，表之间的关系起着重要作用：筛选器传递。这意味着，一个表中某一列的筛选器可以通过关系的交叉筛选方向传递到另一个表，如图4.4所示。\n\n![图示  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image010.jpg)\n\n图4.4 筛选器沿着关系上的箭头方向传递\n\n严格地说，传递的其实不是筛选器本身，而是筛选器的效果：在相关联的表中，只有那些满足筛选规则的行所对应的行才会被选择。当然，这正是我们想要的结果：当切片器设置为图4.3所示的2019年时，我们希望看到2019年的结果，这意味着所有计算都只能在事实表中与2019年的日期相对应的行上进行。\n\n由于查询上下文的性质，我们不能像在行上下文中那样在公式中直接使用列。以下公式作为度量，不被编辑器接受：\n\n```\nReport Year = 'Date'[Year]\n```\n\n这会导致一条错误消息：**无法确定表 'Date' 中列 'Year' 的单个值。当度量公式引用包含许多值的列，且未指定用于获取单一结果的 min、max、count 或 sum 等聚合时，可能发生这种情况。**从概念上讲，原因是所选内容有可能包含多个值。即使列仅包含一个唯一值，或者当表仅包含一行时，也是如此。\n\n### 4.2.3 筛选上下文\n\n筛选上下文看起来类似于查询上下文，但有一个重要的区别：筛选上下文是由 DAX 代码更改的上下文，比如可以在查询上下文中添加或更改筛选器。在此过程中起到核心作用的 DAX 函数是 CALCULATE（或与其同级的 CALCULATETABLE）。使用 CALCULATE 更改上下文的方式将在本章后面的 DAX 筛选：使用 CALCULATE 部分中深入讨论。\n\n查询上下文可以直接通过视觉对象看到，但是筛选上下文是不能直接看到的，因此筛选上下文看起来要难一些。使用筛选上下文需要一定的抽象思维能力，并仔细地分析在特定情况下哪些筛选器处于活动状态。出于同样的原因，有些使用筛选上下文创建的度量值可能会给报表使用者造成疑惑，因此在创建度量值的时候应谨慎一些，例如，使用一目了然的度量值名称。\n\n能够对上下文进行修改，为 DAX 的应用开辟了大量的可能性，而这些可能性仅通过行上下文和查询上下文是无法实现的。它可以给我们提供与查询上下文不对应的结果，并且可用于提供高级见解，例如将产品的销售额与所有产品的销售额进行比较，将今年的销售额与去年同期进行比较，推断未来的趋势，等等。事实上，如果没有筛选上下文，本书第二部分所讨论的所有方案都是不可能实现的。\n\n使用 DAX 创建复杂见解的一般过程可以描述如下。\n\n1. 分析研究将在接下来的计算中使用到的（可能的）查询上下文。\n\n2. 确定期望结果所需的筛选上下文。\n\n3. 确定如何从查询上下文变为筛选上下文。\n\n想要驾驭 DAX，您应该熟悉这种思维方式，这与使用 SQL 检索数据、编程或在 Excel 中执行计算有着根本的不同。\n\n### 4.2.4 检查筛选器\n\n计值上下文中的筛选器会在模型的表中选择某些特定的行。当您考虑这对单个列的影响时，可能会有以下几种情况。有可能并没有进行任何选择，使得列中的所有值都在上下文中。也可能是选择了值的子集，这可能是由该列上的筛选器引起的，在这种情况下，我们定义该列是被直接筛选（Directly Filtered）的。或者它可能是由同一表中另一列的筛选器或另一个表中的筛选器通过关系传递引起的。对于后者，无论筛选器来自哪里，我们都定义该列是间接筛选（Indirectly Filtered）或交叉筛选（Cross Filtered）的。\n\nDAX 包含许多用于检查上下文中的筛选器及其效果的函数。每个函数都将某一列（例如列 A）作为参数。\n\n•   ISFILTERED：检查列A否有直接筛选器。\n\n•   ISCROSSFILTERED：检查模型中任何列上的筛选器是否会导致列A中的筛选。\n\n•   HASONEFILTER：检查列A上的直接筛选器是否只选择了一个值。\n\n•   HASONEVALUE：检查模型中任何列的筛选是否会导致在列A中恰好选择一个值。\n\n•   ISINSCOPE：检查由于视觉对象内部的列 A 上的筛选器是否导致列A中只选择了一个值。此功能旨在检测允许向下钻取的视觉对象中的当前钻取级别。\n\n如果您想查看具体的上下文的内容，这些函数可能会有所帮助。它们还可用于实现特定的 DAX 度量值行为，尽管在此过程中存在一些陷阱。您可以在第5章使用 DAX 构建安全性中找到一些示例。\n\n### 4.2.5 比较查询和筛选上下文与行上下文\n\n既然我们已经介绍了查询和筛选上下文，那么我们就可以从另一个角度来认识行上下文了。例如，假设您在 fSales 表中创建了一个计算列，公式如下。\n\n```\nTotalTax = SUM(fSales[Tax])\n```\n\n您会发现，在生成的 TotalTax 列（总税额列）中，每行都包含相同的值。SUM 函数计算表中所有行的总和，即使我们处于单个行的行上下文中也是如此。对于 DAX 初学者来说，这通常是一个令人惊讶的发现。让我们看另一个示例，这次是 Date 表中的计算列：\n\n```\nTotalShipping = SUM(fSales[ShippingCosts])\n```\n\n同样，您将在每行中找到相同的结果，即使 fSales 表和 Date 表之间存在关系也是如此。这种关系难道不应该对每天的总运费进行计算然后单独返回每一天的值吗？\n\n以上这些示例向我们揭示了行上下文的本质。TotalShipping 示例表明在行上下文中，关系不会传递筛选。这是一个非常有用的经验法则，不过现实情况要更加微妙一些。在行上下文中，DAX 只允许使用同一表中的列值，除此之外，不会选择或筛选任何内容。在计算列中，表中任何列上都没有筛选器。因此，关系无法进行传递。这意味着，当您引用另一个表时（如 TotalShipping 计算），您将使用完整的一张表。即使您引用了计算列所在的表，例如总税计算，也会使用所有的行。\n\n因此，如果您正在使用行上下文，但需要关系进行传递，则必须找到一种方法将行上下文转换为筛选上下文。为此，您必须使用 CALCULATE 函数。\n\n## 4.3 DAX 筛选：使用 CALCULATE\n\n转换上下文是 DAX 最强大的功能之一。用于上下文转换（context transformation）的 CALCULATE 函数是当之无愧的最重要的 DAX 函数。通过在 CALCULATE 中指定筛选器表达式，可以控制公式所处理的行的子集。这可以通过添加或替换筛选器来完成，也可以通过从上下文中删除筛选器来完成。由于关系可以通过筛选器传递在上下文中起重要作用，因此激活或停用关系或更改其筛选器传递行为也是上下文转换的一种形式。\n\n让我们从一个示例 DAX 度量值开始，代码如下。\n\n```\nSalesLargeUnitAmount = \n\nCALCULATE(\n\n  SUM(fSales[SalesAmount]),\n\n  fSales[UnitAmount] > 25\n\n)\n```\n\n此度量值返回已售出超过 25 个单位的交易记录的销售额。CALCULATE 的第一个参数是要执行的计算，在本例中为 fSales 表中 SalesAmount 列的总和。所有其他的参数（可能有很多）都是筛选器参数。\n\n| ![image-20220510133852432](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510133852432.png) | 如果一个 CALCULATE 简单计算过程在单独的一个度量值中，DAX 允许您无需显式地使用 CALCULATE 函数。比如可以写一个简单的sales（销售额）度量值，代码如下。  Sales  = SUM(fSales[SalesAmount])  我们之前看到的示例度量值也可以重新写为：  SalesLargeUnitAmount  =  [Sales]  (fSales[UnitAmount] > 25)  不过，我们建议不要使用这种语法，因为使用显式  CALCULATE 的公式可读性更强，尤其是在一些更复杂的公式中。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n想要透彻理解 CALCULATE 的工作原理，应该时刻牢记它按顺序执行四个基本步骤。\n\n1. 将现有上下文（行上下文或查询上下文，或其他筛选上下文）全部转换为筛选上下文。\n\n2. 筛选器参数中引用的列（或整个表）上，如果有筛选器，那么这些筛选器将被删除。\n\n3. 添加新的筛选器。\n\n4. 在新的筛选上下文中计算第一个参数中的表达式。\n\n有几个特定的 DAX 函数可以放在筛选器参数中使用，从而改变以上的过程。但是一口吃不成胖子，让我们暂时先关注以上这些步骤罢，以 SalesLargeUnitAmount 度量值为例。\n\n### 4.3.1 步骤 1：设置筛选上下文\n\n使用 CALCULATE 时，首先要做的是创建一个可以更改筛选器的环境。如果从查询上下文或筛选上下文开始，这意味着我们已经拥有了环境，所以没什么可做的。因此，对于 SalesLargeUnitAmount 度量值来说，这个过程微不足道。但是，如果我们使用行上下文，那么情况就变得很不同了。\n\n从行上下文到筛选上下文的转换，是通过对表中的每一列创建一个筛选器来实现的，这些筛选器将对应的列中的值指定为当前行中的列的值（请记住，行上下文始终与单个行相关）。结果是生成了一个选择当前行的筛选上下文。除此之外，如果此表与其他表之间存在关系，则这些关系将会传递这些筛选，此时我们也得到在其他表中由被筛选的行所构成的子集。\n\n例如，我们可以通过将之前使用的 TotalTax 计算列用 CALCULATE 包裹起来改一下该列的公式（请注意，我们在这里使用不带任何筛选器参数的 CALCULATE），代码如下。\n\n```\nTotalTax2 = CALCULATE(SUM(fSales[Tax]))\n```\n\n以上公式将每一行中的行上下文都转换为筛选上下文。SUM 函数现在只适用于所选的行，也就是只有当前行[[1\\]](#_ftn1)。换句话说，结果只是行本身中 Tax 列的值。\n\n![表格  中度可信度描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image012.png)\n\n图4.5 计算列中 CALCULATE 的效果\n\n在 TotalShipping 这个例子中的计算列，在 Date 表中，会发生同样的事情，代码如下。\n\n```\nTotalShipping = CALCULATE(SUM(fSales[ShippingCosts]))\n```\n\nDate 表中的行上下文将转换为筛选上下文，该筛选上下文对表的每一列都具有筛选作用。这一次，fSales 和 Date 表之间的关系可以传递所选内容，从而导致 fSales 表中与 Date 表中的当前行相对应的行集被筛选，如图4.6所示。\n\n![表格  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image013.png)\n\n图4.6 CALCULATE 导致关系传递筛选器\n\n添加了 CALCULATE 的结果就是我们得到了正确的每天的 TotalShipping 数量。\n\n| ![image-20220510133930878](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510133930878.png) | 您可能会猜测有多少实际场景中会使用不带筛选器参数的 CALCULATE，这样做的频率比您想象的要高。每次在公式中调用度量值时，CALCULATE  都会隐式地出现，例如：  SalesByCustomer  =  DIVIDE([Sales],  [Number of Customers])  在这个公式中，\"销售额\"和\"客户数\"的计算都是在筛选上下文中完成的。调用度量值是将行上下文转为筛选上下文的常用方法，这是您经常需要执行的操作。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n好了，初始筛选上下文已就绪， CALCULATE 可以执行下一步操作了。\n\n### 4.3.2 步骤 2：删除现有筛选器\n\nCALCULATE 工作顺序中的第二步是从新的筛选上下文中删除筛选器。该过程非常简单：检查其中一个筛选器参数中引用的每个列的筛选器。如果该列上存在筛选器，则会将其删除。\n\n在我们的示例 SalesLargeUnitAmount 度量值中，单个筛选器参数如下。\n\n```\nfSales[UnitAmount] > 25\n```\n\n此筛选器参数导致 CALCULATE 删除 fSales[UnitAmount] 列上的所有筛选器。\n\n有时容易被疏忽的一点是，筛选器参数中未涉及的列会继续保留其筛选器（如果存在）。由于无法完全控制原始上下文的外观，因此在查看度量值可能用于的不同方案时应小心。您可能需要移除比最初预期的更多的筛选器。在您使用DAX的过程中，总会遇到这样的场景，在我们完成所有四个步骤后，其他筛选器可能还会产生影响。\n\n步骤 2 中 CALCULATE 的行为可以通过使用 KEEPFILTERS 函数进行更改。此函数会导致 CALCULATE 对应用 KEEPFILTERS 的筛选器参数跳过步骤 2，例如以下的代码。\n\n```\nSalesLargeUnitAmount KeepFilters = \nCALCULATE(\n  [Sales],\n  KEEPFILTERS(fSales[UnitAmount] > 25)\n)\n```\n\n在此公式中，只要 UnitAmount 列上有现有筛选器，就会保留该筛选器，并在步骤 3 中添加一个新的筛选器。\n\n### 4.3.3 步骤 3：应用新筛选器\n\nCALCULATE 执行的第三步是应用新的筛选器。与步骤 2 一样，该函数遍历其筛选器参数，并将其作为创建新筛选器的说明。在以上示例中，以下的筛选器参数将导致在 UnitAmount 列上创建新的筛选器，选择所有大于 25 的值。\n\n```\nfSales[UnitAmount] > 25\n```\n\n在理解 CALCULATE 时，记住步骤 2 和步骤 3 按该顺序应用是非常有帮助的。筛选器参数本身的顺序无关紧要，如下是一个简单的例子。\n\n```\nSales373_374 = \nCALCULATE(\n  [Sales],\n  Products[ProductID] = 373,\n  Products[ProductID] = 374\n)\n```\n\n许多 DAX 初学者都觉得此公式返回产品ID为 374 的销售额，理由是 ProductID 筛选器首先设置为373，然后又设置为374。实际上，此度量值将始终返回空白，因为是在 ProductID 这一列上添加了两个筛选器，这要求该列同时等于 373 和 374。Products表（产品表）中没有满足这些规则的行，因此 TotalSales 度量值将返回一个空白值（假设存在一个将筛选器从 Products 表传递到 fSales 表的关系）。\n\n您可能会认为这是一个微不足道的例子。但是，稍微改变一下形式，它就会愚弄许多人，请看下面的代码。\n\n```\nSales373OrWhat = \nCALCULATE(\n  [Sales],\n  Products[ProductID] = 373,\n  ALL(Products)\n)\n```\n\n与前面的示例一样，深入理解整个过程发生了什么的正确方法是意识到在添加新筛选器之前每个筛选器参数的筛选器都被移除了。结果是 ProductID 为373的销售额。\n\n### 4.3.4 步骤 4：对表达式进行计算\n\nCALCULATE 工作顺序的最后一步很简单：在设置完筛选上下文、删除筛选器并添加新筛选器之后，我们就可以在新的上下文中计算第一个参数中的表达式了。当然，实践是检验真理的唯一标准，因为这是我们可以真正看到所有上下文转换的效果的地方。\n\n作为筛选器操作如何棘手的示例，请以下面的矩阵视觉对象为例。\n\n![表格  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image016.jpg)\n\n图4.7 示例度量值的输出结果\n\n在此矩阵中，我们使用 Group 列（组列）和 ProductID 列（产品ID列）作为标签来显示有关产品的信息。我们特别关注 Rear wheel 组（后轮组）中的编号为373的产品。在 Product 列（产品列）中此产品的名称是REAR WHEEL STEEL #525（后轮钢#525）。我们希望能够将每个产品的销售额与产品 373 的销售额进行比较。您可以将其视为产品373是我们公司最具战略意义的产品，我们希望将每个产品的销售额表示为产品373销售额的百分比。\n\n若要进行比较，我们需要一个计算，该计算在视觉对象的每一行中都会返回产品 373 的销售额。我们使用两个度量来尝试：\n\n```\nSales373 = \nCALCULATE(\n  [Sales], \n  Products[ProductID] = 373\n)\n```\n\n以及：\n\n```\nSalesRearWheel525 = \nCALCULATE(\n  [Sales], \n  Products[Product] = \"REAR WHEEL STEEL #525\"\n)\n```\n\nSales 度量值同样位于视觉对象中，因此可以更好地查看到底发生了什么。在该视觉对象中的大多数行中，查询上下文中存在两个筛选器：一个位于 Group 列上，另一个位于 ProductID 列上。例外情况是小计行（只有 Group 级别的筛选器）和总计行（没有筛选器）。\n\n显然，使用 CALCULATE 计算的两个度量值返回了不同的结果。为什么会有这种差异呢？由于 Sales373 度量值在筛选器参数中使用了 ProductID 列，因此在添加新的筛选器（步骤 3）之前，将删除该列上的任何现有筛选器（步骤 2）。比如，该视觉对象的产品239这一行上，将删除筛选器“ProductID 等于239”，并添加筛选器“ProductID 等于373”。因此，计算返回了产品373的销售额。\n\nSalesRearWheel525 度量值就不是这种情况了。此处，筛选器参数引用的是 Product 列，因此将删除 Product 列上的任何现有筛选器（步骤 2）。在这之后，添加新的筛选器（步骤 3）。再次查看产品239，查询上下文包含对 Group 和 ProductID 的筛选器。该度量值不会删除这些筛选器，而是在 Product 列上添加新的筛选器。导致的结果就是筛选上下文是 Product 表中满足三个筛选条件的所有行；很显然，除非三个筛选器恰好都指向同一产品，否则不会有任何行被选择，也就是结果为空。三个筛选器恰好都指向同一产品的情况仅适用于产品373本身，也就是为什么视觉对象中只显示了一行数据。\n\n相同的推理过程也可以解释为什么 Sales373 度量值不会在 Rear wheel 以外的组中返回结果：当 Group 上的筛选器选择另一个组时，其与 ProductID 373（新添加的筛选器）组合会导致 Product 表上的选择为空。\n\n### 4.3.5 使用ALL函数清除筛选器\n\n上一节中的两个度量值都存在着相同的问题，很明显，它们都依赖于上下文。若要创建一个无论在查询上下文中选择了什么产品都会始终返回产品 373 销售额的度量值，我们必须摆脱任何可能产生影响的筛选器。\n\n精确控制要移除哪些筛选器非常重要。为此，可以使用一类 DAX 函数，我们将其称为 ALL 系列函数。这些函数之间的区别在于删除了哪些筛选器。\n\n•   **ALL**：此函数可以将一个或多个列或者是一个表作为参数。它会从指定的列中删除筛选器，或者从引用的表中的所有列中删除筛选器。如果确实需要，可以使用不带参数的 ALL 从整个 Power BI 模型中删除所有筛选器。以上情况，代码如下。\n\n```\nALL(Cities[Country])\n\nALL(Cities[Country], Cities[State])\n\nALL(Cities)\n\nALL()\n```\n\n \n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image017.jpg) | 对表使用 ALL 时，从中删除筛选器的列包括相关表中的列。例如，当 fSales 表和 Cities 表之间存在多对一关系时，ALL（fSales） 也会从 Cities 表中删除筛选器。另见ALLCROSSFILTERED。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n•   **ALLEXCEPT**：此函数可用作 ALL 的替代函数，它可以有许多列参数。您可以指定一个表以及该表中想要保留筛选的列，而不是将所有想要删除筛选器的列都写一遍。该函数可以删除表中所有其他列中的筛选器，如下所示。\n\n```\nALLEXCEPT(Cities, Cities[Country])\n```\n\n•   **ALLNOBLANKROW**：使用 ALL 时，生成的上下文将包含指定列中的所有值。这可能会包含由于不完整关系而添加到表中的空白行中的值（请参见第2章 模型设计；这些值必然为空）。如果不希望这些空白值包含在上下文中，则应使用 ALLNOBLANKROW 而不是 ALL。此函数采用一个参数，一列或一个表，如下所示。\n\n```\nALLNOBLANKROW(Cities[Country])\n```\n\n•   **ALLSELECTED**：这是一个特殊的 ALL 函数，因为它是唯一需要关注筛选器来源的函数。只有当筛选器来自于使用度量值的视觉对象中的标签时，ALLSELECTED 才会删除这些筛选器。而来自切片器、页面筛选器或其他视觉效果的外部筛选器则保持不变。此函数用于创建聚合视觉对象中所选项的度量值，例如，在一个视觉对象中的总计行上总是呈现100%。该函数可以使用一个表、一列或多列作为参数，甚至可以像ALL一样没有参数，举例如下。\n\n```\nALLSELECTED(Cities[Country])\n\nALLSELECTED (Cities[Country], Cities[State])\n\nALLSELECTED ()\n```\n\n•   **ALLCROSSFILTERED**：引入此函数是为了在 Power BI 复合模型中，或包含 Direct Query 和导入表的组合的模型，或不同的 Direct Query 连接使用。在此类模型中，不同来源的表之间的关系是“弱关系”，并且并不会按照标准的行为：当 fSales 表是 Direct Query 模式而 Cities 表是导入模式时，ALL（fSales） 不会从 Cities 表中删除筛选器。这时候就需要用到 ALLCROSSFILTERED 函数了，它将表引用作为参数，并将从该表和相关的表中删除筛选器，即使它们之间是弱关系也是如此。在标准的导入模型中，不需要使用 ALLCROSSFILTERED。函数用法如下。\n\n```\nALLCROSSFILTERED(fSales)\n```\n\n\n\n| ![image-20220510134854126](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510134854126.png) | 您可以使用另一个 DAX 函数来删除 CALCULATE 语句中的筛选器：REMOVEFILTERS。此函数将一个或多个列或整个表作为参数，例如：  CALCULATE(  [Sales],  REMOVEFILTERS(Cities)  )  此函数是作为 ALL 的更易于理解的替代函数而引入的。我们更喜欢较短的 ALL，并且从不使用 REMOVEFILTERS。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n通过认真选择一个或多个 ALL 函数，您可以让 CALCULATE 完全按照自己的意愿执行操作。请记住，我们希望创建一个始终返回产品 373 销售额的度量值；换句话说，我们确切地知道我们想要的筛选上下文是什么样子。我们无法控制开始时使用的查询上下文中存在哪些筛选器，但可以控制删除哪些筛选器。请查看以下更新的度量值。\n\n```\nSalesRearWheel525_ALL = \n\nCALCULATE(\n\n  [Sales], \n\n  Products[Product] = \"REAR WHEEL STEEL #525\",\n\n  ALL(Products)\n\n)\n```\n\n通过使用此公式，在将筛选器添加到 Product 列之前，将删除 Products 表中的任何现有筛选器。区别显而易见，如图4.8所示。\n\n![表格  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image020.jpg)\n\n图4.8 使用 ALL\n\n我们可以看到，不仅在 Rear wheel 这一组里所有的产品都返回了产品 373（即REAR WHEEL STEEL #525）的结果，甚至在查询上下文筛选其他产品组时也返回了相同的结果。有了这个，我们就可以表示任何产品相对于产品373的销售比例了，简单的代码如下。\n\n```\nSales% = DIVIDE([Sales], [SalesRearWheel525_ALL]\n```\n\n通过对筛选参数和 ALL 函数进行组合，可以创建相当多的功能强大的 DAX 度量值。不过，仍然有一些筛选器难以创建和指定，其中就包括处理日历的筛选器。这就是 DAX 包含了用于此目的的一类特殊函数的原因，接下来我们就对此展开讨论。\n\n## 4.4 时间智能\n\n几乎所有的 Power BI 模型都会包含一些有关时间的分析。例如，我们希望将当前的结果与去年同期进行比较。可能还需要许多其他与日历相关的见解，例如年初至今（year-to-date）的结果、滚动总计或过去任何其他时间段的增长率。困难在于公历相当混乱：大多数年份有365天，但有些年份有366天，就月份而言，少则28天，多则31天不等。\n\n尽管这些日历很复杂，但基于日历的分析只是筛选以更改上下文。请考虑如图4.9所示的年初至今的销售图表。\n\n![图表, 条形图  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image022.jpg)\n\n图4.9 一个展示年初至今销售额的图表\n\n根据年初至今的定义，您在 August（八月）这一列中看到的是 2021 年 1 月 1 日至 2021 年 8 月 31 日期间的总销售额。但是，此列的查询上下文包含对年份 （2021） 和月份 （8 月） 的筛选，从而导致选择了 2021 年 8 月 1 日至 8 月 31 日这个时间段。显然，必须在此过程中修改上下文，才能够返回年初至今的总销售额。\n\n因此，在年初至今销售额的计算中，您可能会期望使用带着筛选参数的 CALCULATE 来解决，思路如下。\n\n```\nSalesYTD = \n\nCALCULATE(\n\n  [Sales],\n\n  ... (some filter argument)\n\n)\n```\n\n实际上，DAX 时间智能函数在 CALCULATE 中通过筛选参数来处理日历的复杂性。年初至今筛选器由 DATESYTD 函数提供，代码如下。\n\n```\nSalesYTD = \n\nCALCULATE(\n\n  [Sales],\n\n  DATESYTD('Date'[Date])\n\n)\n```\n\nDATESYTD 函数基于日期表上的查询上下文工作。其整个工作过程按照如下所述。\n\n1. 检索上下文中最新的日期。\n\n2. 确定此日期所在的年份以及该年的第一天。\n\n3. 在 Date 表 [Date] 列上创建一个筛选器，选择上下文中从这一年的第一天到最后一天的所有日期。\n\n通过使用新的上下文，CALCULATE 可以完成其计算工作，在我们的示例中，对销售度量值进行计算。但是等等：DATESYTD 筛选器参数引用的是 Date 列，但在图 4.9 中的图表对象中，Year 列和 Month 列上有筛选器！这是一个非常普遍的情况，因此，DATESYTD 还有一个步骤。\n\n4. 添加隐式 ALL（'Date'） 筛选器参数。\n\n最后一步是所有时间智能函数所共有的，这样我们就不必在使用这些函数时添加显式的 ALL 函数了。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image017.jpg) | 只有当你正式地将表标记为 Power BI 模型的日期表时，或者在数据类型为 Date 的列上创建从事实表到日期表的关系时，才会添加隐式  ALL('Date' [Date]) 子句。尽管时间智能函数可以在没有正式声明日期表的情况下正常工作，但我们强烈建议您仍然使用此声明。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n如前所述，时间智能是一个非常普遍的需求。因此，一些时间智能函数也提供了更短、更易于使用的版本。与 CALCULATE 搭配使用的 DATESYTD 函数可替换为一个单独的 TOTALYTD 函数，代码如下。\n\n```\nSalesYTD_short = \n\nTOTALYTD(\n\n  [Sales],\n\n  'Date'[Date]\n\n)\n```\n\n尽管语法不同，但是这个公式在本质上与使用 DATESYTD 函数的公式完全相同。虽然这可能是一个优势，但缺点也很明显，对于许多 DAX 初学者来说，此函数看起来像是只能计算年初至今的总计。实际上，TOTALYTD 所做的唯一的一件事就是改变上下文。它可以返回年初至今的平均值或年初至今的任何想要计算的内容；这完全取决于第一个参数里的度量值或表达式。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image017.jpg) | 财年，一般不将 1 月 1 日作为一年的第一天，为了应用场景的完整性， DATESYTD 和 TOTALYTD 同样适用于这样的日历表。DATESYTD 允许使用第二个参数，它应该能够从中确定一年中的某一天，例如\"8/31\"或\"2020/9/30\"；这被视为一年中的最后一天。不过，有一点我们从未真正理解过，那就是在TOTALYTD中，这个参数是第四个参数，这意味着你必须填入第三个参数。这是一个可选的附加筛选器。您可以在此处放心地使用 ALL（'Date'[Date]），因为无论如何它都会被隐式添加的。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\nDATESYTD 是众多时间智能函数当中使用最多的一个，还有几个频繁使用的，我们简单做一下介绍。\n\n•   **SAMEPERIODLASTYEAR**：顾名思义，该函数采用当前上下文并将其向后移动一年。当然，这是计算同比增长数字所需要的。奇怪的是，该功能没有快捷方式版本。去年的销售额可以通过以下代码来计算。\n\n```\nSalesLY = \n\nCALCULATE(\n\n  [Sales],\n\n  SAMEPERIODLASTYEAR('Date'[Date])\n\n)\n```\n\n•   **DATEINPERIOD**：此函数可用于返回从某个参考日期开始（或结束）的周期，其指定长度以天、月、季度或年为单位表示。这是计算滚动总计所需的函数。例如，使用以下公式计算12个月的滚动销售总额（即回溯12个月）。此处，MAX（'Date' [Date]） 用于检索上下文中的最后一天作为参考日期。\n\n```\nSalesRollingTotal = \nCALCULATE(\n  [Sales],\n  DATESINPERIOD(\n    'Date'[Date],\n    MAX('Date'[Date]),\n    -12, MONTH\n  )\n)\n```\n\n使用时间智能函数时，请务必记住，每次时间智能函数都会转换日期表上的上下文；除此之外，时间智能函数不会有任何其他作用。这意味着模型中与日期表无关的任何表都不会受到此上下文转换的影响。同时，这也意味着，当您的日期表太“短”时，您可能会得到并非您所期望的结果。例如，如果您的日期表开始于 2020 年，并且您在 2020 年 2 月的日期上下文中使用 SAMEPERIODLASTYEAR 函数，则上下文结果一定为空。这将导致度量值的结果为空，即使你聚合的事实表中确实存在着 2019 年或更早的日期。\n\n## 4.5 改变关系的行为\n\n在 第2章 模型设计 中我们介绍过，两个表之间可以建立多个直接关系，但其中只有一个关系可以是活动的。表之间的间接关系路径也是如此：Power BI 模型只允许在模型中的任意两个表之间有一个活动路径。当然，只有当你需要时可以激活这些非活动关系时，这才有用。您可以使用 USERELATIONSHIP 函数来执行此操作。\n\n函数 USERELATIONSHIP 是作为 CALCULATE 中的筛选器参数来使用的。不知道你是否会感到奇怪，USERELATIONSHIP 字面意思是“使用关系”，而 CALCULATE 中的筛选器参数需要表或表表达式，这两者看上去不搭边，但它确实是有道理的。举个例子，某个事实表和筛选表之间的当前活动关系可以将筛选表中的选择传递到事实表中。激活另一个关系意味着，当前所选内容传递到事实表上时会筛选事实表中的不同行。换句话说：激活另一个关系意味着更改计算的上下文。而改变上下文自然要用到 CALCULATE。\n\nUSERELATIONSHIP 函数需要两个参数，是对想要激活的关系的两端的列引用。例如，如果 fSales 和 Date 表之间的活动关系位于 fSales[OrderDate] 上，如果希望改用fSales[InvoiceDate]来建立关系，那么代码如下。\n\n```\nTotalInvoiced = \n\nCALCULATE(\n\n  [Sales], \n\n  USERELATIONSHIP(fSales[InvoiceDate], 'Date'[Date])\n\n)\n```\n\n应该清楚的是，当您使用另一个关系时，计算结果的含义会发生变化。当 Sales 度量值返回订购的金额时，TotalInvoiced （发票总额）度量值返回已开票的金额。前者将被用于收入分析，而后者可能有助于现金流分析（其中实际付款的计算将是一个关键的补充）。当然，这取决于组织对实际销售的业务定义。\n\n更改关系行为的另一种方法是更改活动关系的筛选器传递行为。用于此目的的 DAX 函数是 CROSSFILTER，它同样也是被用于 CALCULATE 中的筛选器参数。\n\n与 USERELATIONSHIP 一样，CROSSFILTER 将关系中涉及的两列作为参数。第三个参数可以设置关系的筛选器传递方向或交叉筛选器类型。可以使用五种交叉筛选器类型。\n\n•   **OneWay**（单向）：沿默认方向传递筛选器，从具有主（唯一）键的表到包含外（非唯一）键的表。\n\n•   **Both**（双向）：在两个方向上传递筛选器。\n\n•   **None**（无）：不传递筛选器。\n\n•   **OneWay_LeftFiltersRight**：沿一个方向传递筛选器，从第一个参数中的列传递到第二个参数中的列[[2\\]](#_ftn2)。\n\n•    **OneWay_RightFiltersLeft**：沿一个方向传递筛选器，从第二个参数中的列传递到第一个参数中的列。\n\n举一个使用 CROSSFILTER 的例子，假设您想知道有多少个state（州）的产品被销售。该模型包含 fSales 表（销售数据表）、Cities 表（城市表）和 Date 表（日期表）之间的关系：\n\n![图形用户界面, 应用程序  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image024.jpg)\n\n图4.10 各城市销售模型图\n\n请注意，从模型图中可以看到，当我们选择了其中一个月，那么该月中的所有销售交易记录都将通过活动关系而被选择。但是，我们无法直接计算 state 的数量：Cities 和 fSales 之间的关系仅将筛选器从 Cities 传递到 fSales，而我们需要向另一个方向传递筛选器。在这种情况下，fSales 表中选定的行将传递选择 Cities 表中的相应行，然后我们就可以计算州的数量。\n\n显然，必须改变关系的筛选器传递方向，DAX 公式如下。\n\n```\nStatesSoldTo = \n\nCALCULATE(\n\n  DISTINCTCOUNT(Cities[State]),\n\n  CROSSFILTER(fSales[CityID], Cities[CityID], Both)\n\n)\n```\n\n使用 DISTINCTCOUNT 函数可以对 State 列中的唯一值进行计数。但在完成此操作之前，CROSSFILTER 会根据在 fSales 表中选择的行来筛选 Cities 表中的行。\n\n## 4.6 DAX 中的表函数\n\n我们可以使用 SUM 和 AVERAGE 等基本聚合函数以及使用 CALCULATE 进行 DAX 筛选来实现许多计算过程。但是 DAX 语言能做的，远远不止这些。本部分将重点介绍表函数，表函数让我们可以更加从容地遨游在更高级 DAX 计算的海洋中。在本书的第二部分，您会发现我们所讨论的许多业务方案都涉及 DAX 表函数。\n\n### 4.6.1 表聚合\n\n首先，让我们看一个 DAX 中的简单聚合运算，请仔细看。\n\n```\nSales1 = SUM(fSales[SalesAmount])\n```\n\n此公式中的 SUM 函数遍历 fSales 表，并从每行中检索 SalesAmount 列中的值。然后将所有这些值都加在一起，并提供最终结果。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image018.jpg) | 由于 Power BI 模型编码和存储数据的特殊方式（详见第2章 模型设计），因此从技术上讲，实际计算过程并不是这样。然而，从逻辑上讲，SUM就是这么做的，我们感兴趣的点也就在于这里。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n|                                                              |                                                              |\n\n \n\n现在，假设 SalesAmount（销售额）列是使用以下公式创建的计算列。\n\n```\nSalesAmount = fSales[UnitAmount] * fSales[SalesPrice]\n```\n\n由于 UnitAmount（数量）列和 SalesPrice（销售单价）列也在 fSales 表中，因此要问的一个有价值的问题是：我们可以在不使用 SalesAmount 列的情况下计算销售额吗？毕竟，我们强烈建议模型中不使用计算列。我们要进行的新计算同样需要遍历 fSales 表，但不应检索 SalesAmount 列中的值，而应从 UnitAmount 和 SalesPrice 列中分别获取数据，并逐个相乘。此处要使用的 DAX 函数是 SUMX，代码如下。\n\n```\nSales2 = \n\nSUMX(\n\n  fSales,\n\n  fSales[UnitAmount] * fSales[SalesPrice]\n\n)\n```\n\n我们之前说过 SUM 函数只接受列引用作为其参数，SUMX 却需要提供一个表，即上面示例中的 fSales 表，第二个参数是要对表中每一行计算的表达式。我们称 SUMX 为**表聚合函数**；你可能还会遇到迭代器这个术语，比如 SUMX 在其第一个参数的表中进行迭代。\n\n大多数基本聚合函数都有一个等效的表聚合函数，如 SUMX、AVERAGEX、MINX、MAXX、COUNTX、COUNTAX、PRODUCTX 和 CONCATENATEX。显而易见，表聚合函数可以通过函数名末尾的 X 来识别。还有一些鲜为人知的表聚合函数，包括像 MEDIANX，PERCENTILEX 和 STDEVX 这样的统计函数（最后两个函数有两种不同的用法，我们这里就不详细展开了，有兴趣的可以自行查阅官方函数说明）。\n\n还有一些表聚合函数，如COUNTROWS，它返回表中的行数，并且没有等效的非表函数。还有 RANKX 是 RANK.EQ 的等效表聚合函数。\n\n如果要去掉表中的计算列，上面的示例非常有参考价值。但是，表聚合函数的真正强大之处在于，您可以使用想要的任何表作为第一个参数。例如，假设您要创建一个返回每个城市的平均销售额的度量值。为此，不能在包含单个销售交易记录的 fSales 表上迭代，而应该迭代 Cities 表，代码如下。\n\n```\nSalesPerCity = \n\nAVERAGEX(\n\n  Cities,\n\n  [Sales]\n\n)\n```\n\n后续我们将讨论这个计算中究竟发生了什么，但让我们首先进一步详细说明可以使用的表。不仅模型中的任何表可以在表聚合函数中使用，甚至可以创建自己要想的特定的表来使用。我们将其称为**虚拟表**（我们本来想使用计算表这一术语，但是它早就被 Power BI 模型占用了）。\n\n### 4.6.2 使用虚拟表\n\n在上一节中，我们阐述过一个计算每个城市平均销售额的公式。现在，假设我们要计算每个州的平均销售额。与 SalesPerCity 度量值一样，我们需要一个每行对应一个州名的表，以便进行迭代。此表在模型中不容易立即获得，因为 State 只是 Cities 表中的列。因此，我们必须自己构造此表。尽管在这种简单的情况下，我们可以向模型中添加一个 State 计算表，但首选方法是创建一个**虚拟表**。此表仅在度量值计值时存在。\n\n有一系列的 DAX 函数可用于创建虚拟表。使用这些函数的一般复杂性在于，它们的结果就是一个表。这意味着没有可用于查看结果的标准输出机制，这一点与度量值不同，我们可以创建一个 Power BI 视觉对象以查看 DAX 度量值的结果是否符合要求。在 Power BI 模型中使用相同的函数创建计算表可能会有所帮助，但无论如何，使用 DAX 表函数需要一定程度的抽象思维。\n\n让我们回到“计算每个州的平均销售额”这个话题，函数 VALUES 将列引用作为其参数，并返回一个具有该列中唯一值的表。代码如下。\n\n```\nVALUES(Cities[State])\n```\n\n这个表表达式返回具有唯一 State 值的表。起到相同作用的函数是 DISTINCT，它也从列中返回唯一值；不同之处在于 DISTINCT 不包含空白值，这些空白值来自于不完整关系导致的空白行（请参见 第 2 章 模型设计中的图 2.5）。是否在结果中显示该空白值应该由您根据实际情况来决定。\n\n每个州的销售额计算如下。\n\n```\nSalesPerState = \nAVERAGEX(\n  VALUES(Cities[State]),\n  [Sales]\n)\n```\n\n\n\n有一系列的 DAX 函数会返回表，但是我们不会在此处全都罗列。我们只将几个最常用的函数展开来说明。\n\n•   **SUMMARIZE**：尽管这是一个多种用途且复杂的函数，能够生成完整的类似数据透视表的结果，但是在 DAX 度量值中它可不是这样使用。此函数可以对 VALUES 函数做一个很好的补充：VALUES 返回一列中的唯一值，而 SUMMARIZE 可以返回多个列中值的唯一组合。例如，fSales 表中 CityID 和 ProductID 值的唯一组合可以通过如下代码获取（请注意，您必须在第一个参数中提供这个表本身）。\n\n```\nSUMMARIZE(fSales, fSales[CityID], fSales[ProductID])\n```\n\n•   **FILTER**：此函数有两个参数，第一个是表（模型中的现有表或另一个表函数的结果），第二个参数是对表中每一行计算的表达式。表达式应产生 true 或 false，并且 FILTER 在结果中仅包含计算结果为 true 的行。例如，下面的表达式返回德国的城市。\n\n```\nFILTER(Cities, Cities[Country] = \"Germany\")\n```\n\n•   **TOPN**：与 FILTER 一样，TOPN 返回表中行的子集。根据某些条件，将返回表中最上面或最下面的几行。您可以提供所需的行数、从中获取行的表、对每行进行排名的值，以及是希望将它们从高到低或从低到高排序。例如，要创建一个销售额排名前 15 的客户的表，代码如下。\n\n```\nTOPN(15, Customers, [Sales], DESC)\n```\n\n•   **CROSSJOIN**：以下介绍的这些函数通过两个输入表创建单个表。 CROSSJOIN 返回两个输入表中每一行的交叉组合的表。下面的示例返回一个包含产品和城市的所有组合的表，其中包含 Cities 表和 Products 表中的所有列。\n\n```\nCROSSJOIN(Cities, Products)\n```\n\n•   **GENERATE**:：与 CROSSJOIN 一样，此函数也会返回一个输入表组合后生成的表。函数的第二个参数是一个表表达式，该表达式针对第一个参数中的表中的每一行进行计算。如果此表达式恰好为特定行返回空表，则该行不会包含在结果中。或者，您可以使用 GENERATEALL，它包含这样的行，但在表表达式的列中包含空白值。例如，下面的表达式返回一个包含城市和产品的表，同样包含 Cities 表和 Products 表中的所有列，其中产品只考虑在该城市有销售额的情况。\n\n```\nGENERATE(Cities, FILTER(Products, [Sales] > 0))\n```\n\n您将在第二部分中遇到一些其他的表函数，我们将在需要用到它们时详细介绍。\n\n### 4.6.3 表函数中的上下文\n\n上面的 GENERATE 示例可能直观上很清楚，但如果你深入思考，它实际上是一个相当复杂的例子。若要清楚知晓此类表达式到底做了什么，理解 DAX 上下文在表函数中的工作方式非常重要。让我们在一个完整的度量值公式中使用 GENERATE 来说明这一点，代码如下。\n\n```\nAvgUnitAmount1 = \nAVERAGEX(\n  GENERATE(\n    Cities,\n    FILTER(\n      Products,\n      [Sales] > 10000\n    )\n  ),\n  AVERAGE(fSales[UnitAmount])\n)\n```\n\n此度量值的目的是计算城市中所有产品销售交易的每笔销售交易的平均产品数量（单位Amount）。这些产品的销量超过10000。你能发现这个公式中的错误吗？\n\n在 Power BI 视觉对象中使用此度量值时，将在查询上下文中对其进行计算。这个上下文可以是任何东西；它可能包含 Power BI 模型中列上的一个或多个筛选器。\n\nAVERAGEX 函数有两个参数，这两个参数各自在不同的上下文中进行计算：\n\n•   第一个参数是表表达式，与 AVERAGEX 函数本身的上下文一致。\n\n•   第二个参数是标量表达式，在第一个参数的表中每一行的行上下文中计算。\n\n您可能已经从前面讨论的 Sales2 度量值中注意到了，该度量值在 SUMX 的第二个参数中使用了直接的列引用。此处的行上下文提供了直接使用表中的列进行计算的可能性。实际上，行上下文转换发生在查询上下文之前，而在计算列中的行上下文中，根本没有筛选器；在这种情况下，查询上下文中的筛选器仍然存在。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image025.jpg) | 使用虚拟表时出现的常见错误与表聚合函数中的行上下文有关。下面是一个简单的示例。  ThisDoesntWork  =    SUMX(      VALUES(fSales[UnitAmount]),  fSales[Tax]  )  虽然此公式使用了 fSales 表中的两列，但却无法直接引用 Tax 列，因为行上下文不在 fSales 表中，而是在VALUES 函数的结果中。此结果是一个只有一列的表，即fSales[UnitAmount]，也只有该列可以直接被使用。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n上面讨论的表函数 FILTER、TOPN 和 GENERATE 的工作方式相同：在调用函数的上下文中计算表参数；另一个参数在行上下文中计值。在以上 GENERATE 这个示例时，这意味着我们在行上下文中计算了一个表表达式。\n\n对于上面的 AvgUnitAmount1 的公式，我们有一系列上下文在起作用。让我们一步一步地分解它们。\n\n1. AVERAGEX：在查询上下文中计算。\n\n2. GENERATE：在与 AVERAGEX 相同的上下文中进行计算。\n\n3. Cities：仍在相同的上下文中进行计算。\n\n4. FILTER：在 Cities 表的行上下文中进行计算。\n\n5. Products：在与 FILTER 相同的上下文中进行计算。\n\n6. [Sales]：由于这是对另一个度量值的调用，因此隐式的 CALCULATE 函数创建了一个筛选上下文。对于每一个调用，都确定了 Cities 表中的一行及在 Products 表中的一行。在筛选上下文中，将添加 Cities 表和 Products 表中每列的筛选器。因此，结果是该产品在该城市的销售情况。\n\n7. AVERAGE：GENERATE 函数返回一个城市和产品组合的表，AVERAGE 在这个表的行上下文中进行计算。\n\n那么这个公式中的错误在哪里呢？\n\n在最后一步：尽管此步骤是针对城市和产品的正确组合进行计算的，但它是在行上下文中计算的。这意味着只有查询上下文中已存在的筛选器才会对 AVERAGE 计算产生影响。当前城市和产品不会影响计算，因为 Cities 表和 Products 表上没有（其他）筛选器来选择当前城市和产品。解决此问题的方法是将行上下文转换为筛选上下文，就像在步骤 6 中所做的那样。我们可以通过添加 CALCULATE 来执行此操作：\n\n```\nAvgUnitAmount2 = \nAVERAGEX(\n  GENERATE(\n    Cities,\n    FILTER(\n      Products,\n      [Sales] > 10000\n    )\n  ),\n  CALCULATE(AVERAGE(fSales[UnitAmount]))\n)\n```\n\n \n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image026.jpg) | 在大多数情况下，最好将平均单位金额计算放在单独的度量中，因为其他地方可能需要用到它。您只需编写一次计算逻辑，此后，对该度量值的调用将自动对行上下文进行转换。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n在 DAX 中设计更复杂的度量值时，仔细跟踪上下文和上下文转换至关重要。\n\n以上所述的这个公式中还有另一个数学计算错误：我们计算平均单位金额的城市/产品组合的平均值。这不一定等于这些城市/产品组合的所有销售交易的平均单位金额。为了解决这个问题，我们需要使用另外一种不同的方法，后文会详细展开说明，让我们先关注另外一个重要的问题：性能。\n\n### 4.6.4 使用表函数的性能注意事项\n\n我们使用 Power BI 的最终目标始终是尽快提供结果，任何时候我们都应该考虑性能问题。因此，在 DAX 中使用虚拟表时，需要时刻注意以下几点。\n\n首先，要认识到虚拟表是由 DAX 引擎在内存中构造的，这一点很重要。这意味着虚拟表越大，需要的内存就越多，性能降低的风险就越高。您甚至可能遇到“**没有足够的内存来执行此计算**”的错误。因此，你应该问问自己，你使用的表是否可以变小：具体来说，你是否真的需要表中所有的列？\n\n在上面的 AvgUnitAmount2 度量中，情况显然并非如此。GENERATE 创建的虚拟表包含Cities 表和 Products 表中的所有列。但对于计算结果而言，实际上只需要表中的唯一键就够了：这些唯一键确定 fSales 中的哪些行被筛选，从而确定 Sales 度量值的计算结果。我们对此进行优化，代码如下。\n\n```\nAvgUnitAmount3 = \nAVERAGEX(\n  GENERATE(\n    VALUES(Cities[CityID]),\n    FILTER(\n      VALUES(Products[ProductID]),\n      [Sales] > 10000\n    )\n  ),\n  CALCULATE(AVERAGE(fSales[UnitAmount]))\n)\n```\n\n我们需要考虑的第二件事是虚拟表中的行数。显然这也是决定表的大小的一个因素，更重要的是，它也同时决定了表聚合中的迭代次数。\n\n例如，如果产品的购买价格存储在 Products 表中，则可以根据 fSales 表计算总采购金额，代码如下。\n\n```\nTotalPurchased1 = \n\nSUMX(\n\n  fSales,\n\n  fSales[UnitAmount] * RELATED(Products[PurchasePrice])\n\n)\n```\n\n相反，您可以通过让 SUMX 迭代 Products 表而不是 fSales 表来优化迭代次数。或者更好的是，迭代 PurchasePrice 列的唯一值，代码如下。\n\n```\nTotalPurchased2 = \n\nSUMX(\n\n  VALUES(Products[PurchasePrice]),\n\n  Products[PurchasePrice] *CALCULATE(SUM(fSales[UnitAmount]))\n\n)\n```\n\n在此变式中，与购买价格值相对应的所有交易记录将被聚合。请注意此处的CALCULATE，将行上下文转换为筛选上下文并筛选正确的销售交易记录。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image026.jpg) | 内存使用情况和迭代次数是 CROSSJOIN 函数在 DAX 度量值中通常不是一个有吸引力的函数的原因之一。CROSSJOIN 返回的行数可能很大，这很容易导致内存问题。在表聚合中包含  CROSSJOIN 时，如下所示（A 和 B 是两个任意表表达式）：  SUMX(    CROSSJOIN(A, B),    [Sales]  )  一个更加便捷的方法是根本不使用CROSSJOIN，代码如下。  SUMX(    A,    SUMX(      B,      [Sales]    )  ) |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n更有效的方法是根本不对虚拟表进行迭代，而是将其用于筛选。这正是下一节的主题。\n\n### 4.6.5 使用表函数进行筛选\n\n长久以来，我们在使用 DAX 时常常感叹于表和筛选之间的深层联系。在本节中，您将了解这个联系是什么，以及如何利用它。\n\n#### 1.使用 CALCULATETABLE\n\n正如我们在本章前面讨论的那样，表聚合函数（如 SUMX）中使用的表表达式的上下文，与表聚合函数本身的上下文是一致的。然而你可能并不总是想要这样的结果：有时，你需要一个不同的上下文。DAX 为此专门提供了一个函数：CALCULATETABLE。\n\n跟它的表兄弟 CALCULATE 一样，CALCULATETABLE 在计算表达式之前会更改上下文。在 CALCULATE 中，此表达式必须返回标量值；在 CALCULATETABLE 中，它必须是表表达式。除了这一点不同之外，该函数也是按照与 CALCULATE 相同的四步过程工作。\n\n1. 设置筛选上下文。\n\n2. 从筛选器参数引用的列或表中删除现有筛选器。\n\n3. 添加在筛选器参数中指定的新筛选器。\n\n4. 计算第一个参数中的表表达式。\n\n通常，CALCULATE 和 CALCULATETABLE 可以互换使用，以下面的公式为例。\n\n```\nAveragePerCity_Canada1 = \nAVERAGEX(\n  CALCULATETABLE(\n    VALUES(Cities[CityID]),\n    Cities[Country] = \"Canada\"\n  ),\n  [Sales]\n)\n```\n\n您可以将此度量值重写为简单的计算公式，代码如下。\n\n```\nAveragePerCity_Canada2 = \nCALCULATE(\n  AVERAGEX(\n    VALUES(Cities[CityID]),\n    [Sales]\n  ),\n  Cities[Country] = \"Canada\"\n)\n```\n\n这两种计算都返回加拿大（Canada）的每个城市的平均销售额（当然，这取决于查询上下文）。但请注意：两者之间存在着技术差异。在第一个公式中，Cities[Country] = \"Canada\" 筛选器应用于 Cities 表的计算，而在第二个公式中，筛选器应用于 Cities 表和 Sales 度量值的计算。虽然在这种情况下，此差异不会影响度量值的结果，但有些时候您可能会用到一些受到此差异影响的更高级的度量值。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image027.jpg) | 与 CALCULATE 一样，CALCULATETABLE 创建了筛选上下文。在计算列中使用时，将在每行中添加新的筛选器以选择该行。在新上下文中计算相关表时，关系会传递筛选器，并且相关表将被筛选为仅链接到当前表的行。这就是为什么用于检索另一个表的相关部分的 RELATEDTABLE 函数只不过是没有筛选器参数的CALCULATETABLE  函数的原因。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n 使用 CALCULATETABLE，可以将筛选器添加到表评估中。有趣的是，您同样可以使用表来添加筛选器。\n\n#### 2.筛选器和表\n\n既然我们已经介绍了表函数，是时候回过头来重新审视筛选器了。之前，我们在查询上下文和筛选上下文中引入了筛选器，作为 Power BI 模型中的列上的“规则”，如“Cities 表 [Country] 列必须等于 France 或 Germany”。您可以将此规则视为 Country 列应包含的值；或者，从另一个角度来看，将其视为具有两行的单列表，其中包含 France 和 Germany。\n\n实际上，这正是筛选器的工作方式以及 CALCULATE 函数的工作模式：通过添加一些定义列中哪些值被选中的表，可能会替换实现筛选的现有表。一个基本的定律如下。\n\n每一个筛选器都是一张表，任何一张表都可以被当作筛选器。\n\n此定律意味着 CALCULATE 中的任何简单筛选器参数都可以重写为一个表。例如，请查看以下公式。\n\n```\nSalesFranceGermany = \n\nCALCULATE(\n\n  [Sales],\n\n  Cities[Country] IN {\"France\", \"Germany\"}\n\n)\n```\n\n此处的筛选器参数等效于下面的表表达式。\n\n```\nFILTER(\n\n  ALL(Cities[Country]),\n\n  Cities[Country] IN {\"France\", \"Germany\"}\n\n)\n```\n\n因此，此筛选器的含义从字面上看是：在所有 Country 的值中，仅选择了 France和 Germany 这两个国家。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image028.jpg) | 这就解释了为什么像下面这样的公式有效：  CALCULATE(    [Sales],    Cities[Country] = \"France\"    \\|\\| Cities[Country] = \"Germany\"  )  虽然这不是最简单的筛选器参数，但它等效于：  FILTER(    ALL(Cities[Country]),    Cities[Country] = \"France\"    \\|\\| Cities[Country] = \"Germany\"  ) |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n大多数作为筛选器引入的 DAX 函数实际上是表函数，正如您已经从上面的 FILTER 表达式中看到的那样，该表达式使用 ALL 作为表函数。实际上，ALL 函数系列都是表函数：ALL(Cities[Country])是一个包含所有唯一国家/地区的单列表，ALL(Cities[Country], Cities[State])是一个两列表，其中包含在 Cities 表中找到的所有唯一的国家/地区和州/省组合。\n\n| ![卡通人物  描述已自动生成](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/clip_image026.jpg) | 并非所有筛选器参数中使用的函数都是表函数：USERELATIONSHIP 和 CROSSFILTER 会更改关系行为，并且不会创建表。KEEPFILTERS 更改了 CALCULATE 的行为，但不能用于创建表。REMOVEFILTERS 的功能类似于筛选器参数中的 ALL，不能用于创建表。 |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n \n\n甚至当我们排除了像 TOTALYTD 这样的快捷方式版本时，时间智能函数也是表函数。每个表都创建一个单列表，其中包含指定时间段内的日期。这意味着您可以在表聚合函数中使用这些函数，例如，计算年初至今每天的平均销售额可以用如下的度量值。\n\n```\nAverageSalesPerDay_YTD = \n\nAVERAGEX(\n\n  DATESYTD('Date'[Date]),\n\n  [Sales]\n\n)\n```\n\n筛选器即是表 这一定律的更令人兴奋的用途是，您可以使用任何表（包括虚拟表）作为 CALCULATE 函数中的筛选器。举个简单的例子，假设您希望有一个度量值来返回所选城市所在的一个或多个国家的总销售额。如果您确定在此计算的查询上下文中，Country 列被筛选，则下面的公式并不难理解。\n\n```\nSalesWholeCountry1 = \n\nCALCULATE(\n\n  [Sales],\n\n  ALLEXCEPT(Cities, Cities[Country])\n\n)\n```\n\n此计算将从 Cities 表中去掉除 Country 列之外的所有列上的筛选器。因此，如果查询上下文包含筛选器“在 City 列上选择了亚特兰大”和“在 Country 列上选择了美国”，则生成的筛选上下文只剩下“在 Country 列上选择了美国”这一个筛选器。\n\n但是，如果查询上下文一开始就只有“在 City 列上选择了亚特兰大”这一个筛选器，那么我们就会遇到麻烦：删除此筛选器意味着去掉了所有的筛选，返回了所有的数据！为了解决这个问题，我们需要重新输入一个“在 Country 列上选择了美国”的筛选放到上下文中，并且它应该派生自 City 列上的筛选器。这可以通过表筛选来完成，代码如下。\n\n```\nSalesWholeCountry2 = \n\nCALCULATE(\n\n  [Sales],\n\n  ALL(Cities),\n\n  VALUES(Cities[Country])\n\n)\n```\n\n想要看明白此处到底发生了什么，需要深刻地理解这一点：筛选器参数计算所需的上下文与 CALCULATE 本身上下文完全一致。在仅选择一个城市的查询上下文中，VALUES(Cities[Country]) 表达式返回一个单列表，其中包含该城市所在的国家。这正是我们实现想要的计算所需的筛选器。\n\n再举个例子，下面的公式计算销售额前 10000 名的客户的总销售额。\n\n```\nSalesLargestCustomers1 = \n\nCALCULATE(\n\n  [Sales],\n\n  TOPN(10000, ALL(Customers), [Sales], DESC)\n\n)\n```\n\n请注意，我们在这里使用 ALL(Customers) 来排除可能会在查询上下文中存在着一些筛选器，这些筛选器返回的是客户的子集。TOPN 函数返回销售额前 10000 名的客户（作为 Customers 表的子集，包括了所有列，当然您可能希望在此处删除不需要的列）。然后，此表将用作筛选器。\n\n此公式清楚地表明了为什么使用表筛选比使用表聚合更可取。如下所示的代码是此度量值的表聚合替代方法。\n\n```\nSalesLargestCustomers2 = \n\nSUMX(\n\n  TOPN(10000, ALL(Customers), [Sales], DESC),\n\n  [Sales]\n\n)\n```\n\n请认真想一下这个问题：在进行这两个计算时分别调用了多少次 Sales 度量值？当然，这取决于我们的 Customers 表中的客户数量。假设我们有 60000 个客户。TOPN 函数必须为每个客户都调用一次 Sales 度量值，以确定哪些是销售额最大的客户。完成此操作后，SalesLargestCustomers1 度量值只需要对 Sales 进行一次额外的调用：TOPN 表将作为筛选器，从而为销售额最大的客户创建上下文。但是，SalesLargestCustomers2 度量值将遍历 TOPN 表并为每行再次调用 Sales。换句话说，此度量值总共调用了 Sales度量值 70000 次，而 SalesLargestCustomers1 度量值仅调用 60001 次！即便 DAX 引擎可能会优化此处的计算过程，但其中的差异依旧会很大。\n\n与查询上下文中的筛选器不同的是，表筛选器可以具有多个列，当您意识到这一点时，将表用作筛选器将变得更加强大。这意味着，本章前面部分中我们讨论过的仍然存在问题的 AvgUnitAmount3 度量值，现在有了一个解决方案，代码如下。\n\n```\nAvgUnitAmount4 = \nCALCULATE(\n  AVERAGE(fSales[UnitAmount]),\n  GENERATE(\n    VALUES(Cities[CityID]),\n    FILTER(\n      VALUES(Products[ProductID]),\n      [Sales] > 10000\n    )\n  )\n)\n```\n\n这次，我们使用 GENERATE 来提供一个筛选器，用于选择销售额超过 10000 的城市和产品的组合，而不是 GENERATE 表达式上表聚合的平均值。然后，我们计算与所选城市/产品组合相对应的所有销售交易的平均单位金额。现在，我们不仅有了正确的计算平均的方法，而且还消除了对 GENERATE 表的迭代，这将有助于进一步提高此度量值的性能。\n\n### 4.6.6 使用 TREATAS\n\n使用表筛选器时有一个重要的约束：这些表必须真正地筛选执行计算的表。如果添加一个与模型其余部分没有任何关系的表筛选器不会执行任何操作。\n\n例如，以下公式并不会返回英国的销售额。\n\n```\nUKSales_wrong = \n\nCALCULATE(\n\n  [Sales],\n\n  ROW(\"Country\", \"United Kingdom\")\n\n)\n```\n\n为什么这不起作用呢？原因是 Power BI 模型无法确定使用 ROW 函数创建的这个随性的表中，到底哪一列的名字是 Country，它应筛选 Cities 表，该表同样也包含 Country 列）。你可能会说，“哎呀，字段名称是相同的，所以 DAX 引擎应该可以假设这就是公式的本意吧”；如果真的是这样，一些模型在许多不同的表中可能具有相同的列名，这可能会导致一些完全不可预知的结果。\n\n为了能够被用作筛选器，DAX 引擎应该能够识别虚拟表是否连接到模型中的表或某些列。这种连接称为**数据沿袭**（Lineage），简而言之，这意味着在创建虚拟表时，DAX 会跟踪虚拟表中列的来源的原始列。让我们再看一次 GENERATE 这个表达式。\n\n```\nGENERATE(\n  VALUES(Cities[CityID]),\n  FILTER(\n    VALUES(Products[ProductID]),\n    [Sales] > 10000\n  )\n)\n```\n\n很明显，Cities[CityID] 是模型中的一列，并且由于 VALUES 从该列中获取唯一值，因此 VALUES(Cities[CityID])具有该列的数据沿袭。同样的道理，VALUES(Products[ProductID])与 Products 表中的 ProductID 列具有数据沿袭。GENERATE 函数创建了一个表，其中包含两个 VALUES 表达式中的值组合，因此生成的表中的每一列都具有与相应的模型列一致的数据沿袭。\n\n大多数表函数会保留它们来源的列的数据沿袭。但是，某些函数允许以奇怪的方式形成新的表，这在数据沿袭方面可能存在问题。例如，UNION 函数允许从两个源表中获取行来组合成为一个新的表，这两个表可能具有冲突的数据沿袭。如果是这样，则结果表中的列与模型中的任何现有的列都没有数据沿袭。\n\n在某些情况下，您也可能希望虚拟表的数据沿袭与默认值不同。DAX 通过 TREATAS 函数提供了一个解决方案，该函数强制模型中某个表的列具有特定的数据沿袭。\n\nTREATAS 是专门用在 CALCULATE 或 CALCULATETABLE 函数中作为筛选器参数的另一个例子。下面的公式正确计算了英国的销售额（尽管有更简单的方法可以做到这一点）。\n\n```\nUKSales_correct = \nCALCULATE(\n  [Sales],\n  TREATAS(\n    ROW(\"Country\", \"United Kingdom\"),\n    Cities[Country]\n  )\n)\n```\n\n请注意，TREATAS 不要求列的名称相同。我们可以在 ROW 表达式中将列命名为我们想要的任何名称。TREATAS 也适用于多列的表，在这种情况下，应为创建的表中的每一列指定一个模型中的列。您可以在第 9 章 公司间业务 中找到使用 TREATAS 的综合示例。\n\n### 4.6.7 DAX 变量\n\nDAX 表函数和筛选大大提高了使用 DAX 可以完成的计算的复杂性。然而有利就有弊，公式可能会因此变得很长。更重要的是，在整个度量值的书写过程中，不同位置的上下文可能完全不同，在得到正确结果的道路上往往会出现各种问题。\n\nDAX 变量，使得这类设计高级 DAX 代码的工作变得轻松了不少。该名称有些奇怪，因为 DAX 变量的用途是，您可以计算一次某些内容，稍后在其他情况下（通常是其他上下文）使用它，而不必担心变量的计算。换句话说，DAX 变量被用作常量！\n\n变量是使用 VAR 关键字声明的。可以声明多个变量，并且一个变量的声明可以使用之前声明的另一个变量的值。变量的声明由 RETURN 关键字来关闭。\n\n```\nVAR ThisValue = 5\n\nRETURN\n\n...\n```\n\n知道 DAX 变量可用于 DAX 公式中的任何表达式是有必要的。变量可以包含标量值，但也可以是表。下面的（看上去相当荒谬的）公式是一段正确的 DAX 代码。\n\n```\nVariableTest = \nVAR Variable1 = 3\nVAR Variable2 = Variable1 + 5\nRETURN\nCALCULATE(\n  VAR Variable3 = MAX(fSales[UnitAmount])\n  RETURN\n  SUM(fSales[Tax]) + Variable2 + Variable3,\n  VAR Variable4 = 4\n   VAR TableVariable =\n    FILTER(\n      ALL(fSales[UnitAmount]),\n      fSales[UnitAmount] = Variable4\n    )\n  RETURN\n  TableVariable\n)\n```\n\n变量声明在公式中的位置决定了计算变量的上下文。例如，上面的 Variable3 变量是在通过将 TableVariable 表筛选器应用于此度量值的原始查询上下文而形成的筛选上下文中计算的。如果 Variable3 是在 Variable2 之后声明的，那么它将在查询上下文中进行计算。注意 Variable4 和 TableVariable 在 CALCULATE 的筛选器参数中使用；两者都在原始查询上下文中进行计算。\n\n每个变量都有自己的作用域，这意味着它不能在声明它的表达式之外使用。在上面的公式中，Variable3 不能用于定义 TableVariable；Variable3 是 CALCULATE 的第一个参数，在其他位置，它是无效的。相反，Variable4 和 TableVariable 不能在 CALCULATE 的第一个参数中使用。Variable1 和 Variable2 是整个表达式的一部分，可以在任何地方使用。\n\nDAX 变量不仅可以帮助简化计算流程，还可以使公式更具可读性，只需使用清晰的变量名称即可。让我们再次回顾一下 AvgUnitAmount4 的度量。\n\n```\nAvgUnitAmount4 = \nCALCULATE(\n  AVERAGE(fSales[UnitAmount]),\n  GENERATE(\n    VALUES(Cities[CityID]),\n    FILTER(\n      VALUES(Products[ProductID]),\n      [Sales] > 10000\n    )\n  )\n) \n```\n\n如果使用变量，可以将此公式进行简化，代码如下。\n\n```\nAvgUnitAmount5 = \nVAR LargeCityProductCombinations =\n  GENERATE(\n    VALUES(Cities[CityID]),\n    FILTER(\n      VALUES(Products[ProductID]),\n      [Sales] > 10000\n    )\n  )\nRETURN\nCALCULATE(\n  AVERAGE(fSales[UnitAmount]),\n  LargeCityProductCombinations\n)\n```\n\n\n\n永远记住，DAX 变量是常量！下面的变体不会得到正确的结果。\n\n```\nAvgUnitAmount_wrong = \nVAR LargeProducts =\n  FILTER(\n    VALUES(Products[ProductID]),\n    [Sales] > 10000\n  )\n\nVAR LargeCityProductCombinations =\n  GENERATE(\n    VALUES(Cities[CityID]),\n    LargeProducts\n  )\n\nRETURN\n\nCALCULATE(\n  AVERAGE(fSales[UnitAmount]),\n  LargeCityProductCombinations\n)\n```\n\n通过将 FILTER 表达式放在变量中，我们将其转换为常量表。然而，在 GENERATE 函数中，我们却希望为每个城市重新确定产品列表，因此结果必然是错误的。\n\n## 总结\n\n在本章中，您已经了解了行上下文、查询上下文和筛选上下文，以及上下文在评估 DAX 公式时所起到的作用。我们已经讨论了如何使用 CALCULATE 函数通过删除筛选器并将筛选器添加到现有上下文中来转换上下文。此外，我们还研究了时间智能函数，这些函数提供了专门针对公历量身定制的筛选器。\n\n接着，我们重点介绍了 DAX 表函数，这些函数使我们能够聚合表以及在 DAX 公式中使用自定义的虚拟表。使用虚拟表在使用“标准”的 DAX 函数和筛选之前提供了丰富的分析功能。我们讨论了表和筛选器之间的深层次联系，这允许我们将任何表用作筛选器。最后，我们讨论了 DAX 变量，通过使用这些变量可以更轻松地在 DAX 中实现复杂逻辑，并提高 DAX 代码的可读性。\n\n所有这些都是使用 DAX 探索更高级分析所需的基本概念。在这关键的一章之后，本书的第二部分将专注于将以上讨论的所有概念应用于实际的商业案例。我们的期望是，通过浏览这些案例，您将进一步领略并理解 DAX 的强大功能，由此您将受到启发，并使用 DAX 计算来解决自己的业务问题。\n\n第 5 章 基于 DAX 的安全性 专门介绍 Power BI 模型中的安全性。在这一章，您将看到，在设计安全性时，DAX、上下文和筛选方面的知识早就已经找到了许多用武之地。\n\n\n\n------\n\n注释：\n\n1 译者注：此处应注意，并不是在所有情况下都 “只”筛选当前的行。在本例中所展示的数据，因为不存在完全相同的行，因此每一行上由行上下文转换而来的筛选上下文都不相同，这些筛选上下文起作用时只会将这个唯一行筛选出来。但是，如果表中的两行（甚至多行）内容完全相同，那么每一行上的由行上下文转换而来的筛选上下文也完全相同，并且都筛选了两行（或所有相同的行），结果很显然会是错误的。并且本例只是作者用来阐述上下文转换过程的一个简单示例，在实际的业务场景中并不会真正出现。因此，这种计算方法（仅对于使用SUM求和而言）并不建议使用。\n\n2 译者注：此选项不能与一对一关系或多对一关系同时使用。OneWay_RightFiltersLeft同样也是如此。"
  },
  {
    "path": "_posts/2022-05-10-Power Automate Flow中JSON的增删改查.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-parefreshpowerbi.jpg\ncatalog: true\ntags:\n    - PowerAutomate\n    - PowerBI\n---\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n\n\njson是powerautomate云端flow中常常出现的一种数据形式，有时需要手动生成，有时需要自动获取后进行获取其中的内容。\n\njson的增删改查熟练对于快速构建一个有效的flow大有裨益。\n\n\n\n我们以一个云端流为例简单地说一下关于json的操作。\n\n\n\n1、增addProperty\n\n首先我们需要先创建一个变量-json示例：\n\n![image-20220510190826485](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510190826485.png)\n\n此处的{}是有必要的，否则会运行不成功。\n\n设置有一个编辑：\n\n```\naddProperty(variables('json示例'),'姓名','张三')\n```\n\n![image-20220510191008303](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510191008303.png)\n\n接着我们还得将此结果返回到变量中：\n\n![image-20220510191021559](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510191021559.png)\n\n输出结果为：\n\n![image-20220510192242047](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510192242047.png)\n\n不过很多时候，我们想要往里添加的内容不止这么简单，我们可能想要添加另一个json到这个json中，形成嵌套。方法也很简单，再设置一个变量地址\n\n![image-20220510191747592](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510191747592.png)\n\n再次使用addProperty：\n\n```\naddProperty(variables('json示例'),'地址',variables('地址'))\n```\n\n输出：\n\n![image-20220510192304559](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510192304559.png)\n\n我们还可以继续往里添加一些内容，比如邮编：\n\n```\naddProperty(outputs('编辑_2'),'邮编', '266500')\n```\n\n输出：\n\n![image-20220510192319479](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510192319479.png)\n\n\n\n2、删removeProperty\n\n某些时候我们需要删除json结构中的某些字段，就可以使用removeProperty来实现，用法如下：\n\n```\nremoveProperty(outputs('编辑_3'),'姓名')\n```\n\n输出：\n\n![image-20220510192340472](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510192340472.png)\n\n\n\n3、改setProperty\n\n如果要对json中的某项内容进行修改，可以使用setProperty，比如要修改邮编为266555：\n\n```\nsetProperty(outputs('编辑_4'),'邮编','266555')\n```\n\n输出：\n\n![image-20220510195453765](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510195453765.png)\n\n如果json结构中没有setProperty设置的字段，那么会添加一个新的字段，效果与addProperty一致：\n\n```\nsetProperty(outputs('编辑_5'),'姓名','学谦')\n```\n\n输出：\n\n![image-20220510193108702](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510193108702.png)\n\n4、查\n\n如果我们想由此json结构得到里面姓名字段的值，可以有多种办法，可以使用“分析json”这个独立的功能，\n\n![image-20220510200347287](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510200347287.png)\n\n然后选取“姓名”字段：\n\n![image-20220510200449844](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510200449844.png)\n\n\n\n我们也可以直接按照如下的写法（本质与分析json相同）：\n\n```\noutputs('编辑_6')?['姓名']\n```\n\n输出：\n\n![image-20220510193117886](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510193117886.png)\n\n\n\n如果想获取子结构中的字段的值也是可以的：\n\n```\noutputs('编辑_6')?['地址']?['城市']\n```\n\n输出：\n\n![image-20220510200839253](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220510200839253.png)\n\n\n\n以上就是powerautomate云端flow的json结构增删改查的全部内容，通过本文的学习，想必你一定会对json结构的数据处理更加得心应手。\n\n\n\n加入知识星球，学习PowerAutomate的最新知识与技能，与众多伙伴一起探讨新的玩法，优惠转瞬即逝，还不快大力把握它@！\n\n![海报 (5)](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%B5%B7%E6%8A%A5%20(5).png)\n"
  },
  {
    "path": "_posts/2022-05-11-Extreme DAX中文目录.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-extremedax.jpg\ncatalog: true\ntags:\n    - Extreme DAX\n---\n\n\n\n公众号：PowerBI生命管理大师学谦，同步更新，敬请关注\n\n\n\n目录：\n\n[Extreme DAX中文  前言](http://powerbipro.cn/2022/04/08/Extreme-DAX中文第0章-前言/)\n\n[第一章：商业智能中的DAX](http://powerbipro.cn/2022/04/08/Extreme-DAX%E4%B8%AD%E6%96%87%E7%AC%AC1%E7%AB%A0-%E5%95%86%E4%B8%9A%E6%99%BA%E8%83%BD%E4%B8%AD%E7%9A%84DAX/)\n\n[第二章：模型设计](http://powerbipro.cn/2022/04/08/Extreme-DAX%E4%B8%AD%E6%96%87%E7%AC%AC2%E7%AB%A0-%E6%A8%A1%E5%9E%8B%E8%AE%BE%E8%AE%A1/)\n\n[第三章：DAX的用法](http://powerbipro.cn/2022/04/20/Extreme-DAX%E4%B8%AD%E6%96%87%E7%AC%AC3%E7%AB%A0-DAX%E7%9A%84%E7%94%A8%E6%B3%95/)\n\n[第四章：上下文和筛选](http://powerbipro.cn/2022/05/10/Extreme-DAX中文第4章-上下文和筛选/)\n"
  },
  {
    "path": "_posts/2022-05-12-Power Automate实现PowerBI数据集刷新结束后通知.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-parefreshpowerbi.jpg\ncatalog: true\ntags:\n    - PowerAutomate\n    - PowerBI\n---\n\n\n\n本文为PowerBI REST API高级应用教程，需要有REST API基础，并且能够自行获取token的基础上进行操作。\n\n实际的业务场景往往纷繁复杂，比如某个时候你需要将最新的数据呈现给甲方爸爸，在按了一次刷新之后，在漫长的数据集刷新过程中，可能需要一次次点开网页刷新，看看是否已经刷新结束，往往消磨了人们的耐性。示例的文件刷新15分钟已经够客气了。\n\n![image-20220511174430302](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220511174430302.png)\n\n当然，你可以在每次刷新时设置一个15分钟的闹钟，以便提醒，但是未免太过繁琐。并且不是每次的刷新都是15分钟，往往有些时候可能需要更长的时间。\n\n如果能有一个办法在每次刷新结束时自动提醒我就好了！\n\n有人说可以通过数据预警，但是数据预警只能设置每天或者每小时发通知一次，而且设置思路并不是很明确。\n\n### 一、本文提供的思路是：\n\n**当前时刻，以往每次刷新的状态是可以获取的，通过API。**\n\n刷新状态一共有三个，Completed（成功），Failed（失败），Unknown（未知，即正在刷新）。\n\n也就是说，可以通过刷新状态的变化，来确定什么时间刷新结束。\n\n比如一次刷新大约需要15分钟，那么我可以设置一个10分钟一次检测的flow(该时间间隔一定要小于数据刷新的时间，否则有一定几率漏掉)，获取最后一次刷新的状态。\n\n如果状态不为Unknown，跳过；\n\n否则进入小循环，5秒检测一次，直到状态转为Completed，结束，发送邮件通知。\n\n### 二、具体设置过程：\n\n#### 1、触发\n\nPower BI刷新开始并没有直接或间接的触发条件（可能是我孤陋寡闻了，如有高见，请不吝指教），如果是每天固定的计划刷新，那么可以可以设置在某个时间段开始运行flow；如果是手动触发，也是有办法的，比如报告上使用一个flow来触发，dataset刷新启动后下一步就是这个操作。具体可以参考这篇文章。\n\n但是很多时候可能既有手动刷新也有定时刷新，并且定时刷新时间也会有所变动， 此时上面的办法就不行了。\n\nflow的触发可以使用定时，比如10分钟检测一次，如果状态不为Unknown则跳过。但是这里面有个逻辑，比如一个dataset刷新从14:02刷新到14:17，那么如果在14:05定时触发检测到状态为Unknown，则进入小循环，等到14:17刷新结束时一定会收到提醒邮件，这个没问题；但是14:15时定时运行的flow又会再一次运行另一个进程，依然会检测到Unknown，依然会进入小循环，并在14:17时发送另一个提醒邮件。\n\n也就是说， 通过这种方式定时运行flow会有一定的小问题。\n\n那么我们如何改善这一点呢？\n\n答案是，手动触发。\n\n有同学会问了，手动触发不就是一次性的吗？难道每次刷新都需要手动触发？\n\n并不是！\n\n我们这里使用了一个技巧，套用了两个loop，每个loop设置5000次（这也是limits的最大值），这样就是25000000次循环：\n\n![image-20220512173834139](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220512173834139.png)\n\n\n\n按照设定的10分钟delay一次，大约可以运行475.6年……嗯，基本够用。\n\n如果你非说不够用，就再套个5000次！\n\n#### 2、获取状态\n\nAPI获取刷新状态是一个基本的操作：\n\n```\nGET https://api.powerbi.com/v1.0/myorg/groups/{groupId}/datasets/{datasetId}/refreshes?$top={$top}\n```\n\n这篇文章中有所介绍：\n\nhttps://mp.weixin.qq.com/s/sb22c-bKgy7XsN_rlOQwdg\n\n此处用了一个$top=1，即获取最后一次的刷新即可。\n\n获取的内容是一个json，关于json的处理这篇文章有所介绍：\n\nhttps://mp.weixin.qq.com/s/pNWqakSGXQdEyc6KRWzaRA\n\n```\nfirst(body('HTTP_获取刷新历史')?['value'])?['status']\n```\n\n\n\n![image-20220512175314234](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220512175314234.png)\n\n\n\n#### 3、一旦识别了Unknown，进入小循环\n\n加一个条件判断，如果最后一次刷新状态是Unknown，进入小循环，5秒获取一次，直到状态改变：\n\n![image-20220512181827410](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220512181827410.png)\n\n\n\n状态改变代表着刷新结束，当然，结束有多种方式，Completed只是其中一种，也可能会失败。但是不管刷新结果是什么，我们都会收到邮件的提醒。\n\n#### 4、实操展示\n\n我分别在17:12，17:28和17:51进行了刷新：\n\n![img](https://images.zsxq.com/FrzcsN6Cw6_xcGPI-pY-JA0KeJxA?imageMogr2/auto-orient/quality/100!/ignore-error/1&e=1656604799&token=kIxbL07-8jAj8w1n4s9zv64FuZZNEATmlU_Vm6zD:9wdJWQGqOiEBAsiXgG4EwTh0wqI=)\n\n刷新结束时都收到了邮件提醒，3次刷新都成功：\n\n![img](https://images.zsxq.com/FpF3cv1fCm_IJlnlfGRya0fumpYZ?imageMogr2/auto-orient/quality/100!/ignore-error/1&e=1656604799&token=kIxbL07-8jAj8w1n4s9zv64FuZZNEATmlU_Vm6zD:Qc9KcGtGuewDlCHhnheJsdTcO7Q=)\n\n\n\n### 三、总结\n\n本文讲解了使用PowerBI REST API配合PowerAutomate实现PowerBI报告刷新结束时邮件通知的方法。前提是能够使用Azure AD设置应用程序，调用API，并且需要PowerAutomate的高级版应用。\n\n有需要的朋友可以自行测试。本文提供PowerBI API付费技术指导，请直接联系学谦。\n\n\n\n### 四、广告时间\n\n1、学谦PowerBI知识星球：\n\n全年365天无休，随时提问答疑，与众多伙伴一起学习交流PowerBI技术，更有大佬嘉宾坐镇，可以直接提问。\n\n![海报 (6)](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%B5%B7%E6%8A%A5%20(6).png)\n\n\n\n\n\n\n\n2、加入PowerAutomate知识星球，学习PowerAutomate的最新知识与技能，与众多伙伴一起探讨新的玩法，优惠转瞬即逝，还不快大力把握它@！\n\n![海报 (5)](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%B5%B7%E6%8A%A5%20(5).png)\n"
  },
  {
    "path": "_posts/2022-05-16-Power BI 以小易大-破电脑也能搞定大模型.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-bigsmall.png\ncatalog: true\ntags:\n    - PowerBI\n---\n\n### 一、背景\n\n数据集过大，尤其是在电脑配置不怎么高的情况下，Power BI desktop的刷新过程往往是漫长的，很多时候往往卡在这里不动了：\n\n![image-20220517160349124](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160349124.png)\n\n比较中肯的建议是：~~换电脑！~~\n\n本地刷新使用小表，云端刷新使用大表。\n\n即，在本地运行desktop时使用较小的数据集制作模型，修改模型，一旦发布到service，将数据集切换到大数据集，利用云端高效的服务器进行刷新并计算。\n\n### 二、设置过程\n\n本文先以从本地获取文件夹为例：\n\n比如此处有两个文件夹，【数据表】和【数据表-小】：\n\n![image-20220517160420479](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160420479.png)\n\n【数据表】内有360个文件：\n\n![image-20220517160431973](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160431973.png)\n\n而【数据表-小】中只有2个文件：\n\n![image-20220517160441373](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160441373.png)\n\n数据量不可同日而语。\n\n本地制作powerbi报告时，我首先使用【数据表-小】这个文件夹，并且文件夹路径是通过设置一个参数来实现：\n\n![image-20220517160504614](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160504614.png)\n\n因此获取文件夹时就使用这个参数：\n\n![image-20220517160515557](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160515557.png)\n\n测试了一下当前的刷新速度，很快，几秒钟时间；\n\n将参数改为【数据表】，在我的3700X+32G内存电脑上刷新了4分钟左右；而在另一台联想低配一体机上从开始写这篇文章开始到现在接近10分钟了还没刷新完。\n\n![image-20220517160525853](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160525853.png)\n\n继续写着，等等它！\n\n然后制作报告，并发布成功！\n\n接下来我们到云端，刷新一下，看看时间，12秒还是很快的：\n\n![image-20220517160537781](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160537781.png)\n\n我们在数据集的设置中找到这里的参数：\n\n![image-20220517160547038](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160547038.png)\n\n将其改为【数据表】，并应用：\n\n![image-20220517160557093](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160557093.png)\n\n云端刷新一下，接近4分钟：\n\n![image-20220517160608652](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517160608652.png)\n\n基本上与我的3700X计算速度差不多。而此时一体机刚刚结束本地刷新，差不20分钟。\n\n### 三、结论\n\n通过参数改变数据的文件夹路径，可以有效地节省本地desktop上编辑模型时熟悉数据所需的时间，从而让我们更加从容地将主要精力放在模型的建设本身。\n\n### 四、悬念\n\n本文解决的是文件夹形式的数据源。那么如果不是这种类型的呢？比如onedrive for business上的文件夹，或者数据库又该如何去做呢？\n\n关注学谦，下期分享给你！\n\n### 五、广告时间\n\n全年365天无休，随时提问答疑，与众多伙伴一起学习交流PowerBI技术，更有大佬嘉宾坐镇，可以直接提问。\n\n![图片](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjGgQG5QkMBfpqGa2sh3vGlzg3Q0s4iaQH1jJ1BbhprIXSI9SRbHibs7W7wqo0wX6c66smKl9SJia8DA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1)\n"
  },
  {
    "path": "_posts/2022-05-16-它来了，它来了，Power BI的5月更新带着“字段参数”向你走来了.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-parameter.jpg\ncatalog: true\ntags:\n    - PowerBI\n---\n\n\n\n搁在以前，想实现下面这个图，那可真是费心费力。\n\n- 要么整几个书签来回交互\n- 要么各种Switch来回切换\n- 要么crossjoin各种辅助表\n\n\n\n![2](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/2.gif)\n\n下边这个公式都算是客气的\n\n![image-20220517150443455](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150443455.png)\n\n幸好！\n\n最新一期的PowerBI更新带着大招【字段参数】来了！\n\n![image-20220517150607391](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150607391.png)\n\nhttps://powerbi.microsoft.com/en-us/blog/power-bi-may-2022-feature-summary/\n\n\n\n需要在预览功能中将字段参数这个勾上：\n\n![image-20220517150619991](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150619991.png)\n\n这样，在新建参数这里就会出现一个【字段】：\n\n![image-20220517150627731](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150627731.png)\n\n点击字段出现参数设置：\n\n![image-20220517150637846](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150637846.png)\n\n拖拽相应的字段到左侧即可：\n\n\n\n![image-20220517150648992](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150648992.png)\n\n\n\n报告页面上会自动出现一个切片器：\n\n![image-20220517150657781](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150657781.png)\n\n\n\n然后放一个柱状图对象，将x轴设置为【参数】，y轴设置为度量值：\n\n![image-20220517150705268](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150705268.png)\n\n\n\n这样我们就可以随意地改变坐标轴了！\n\n![1](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/1.gif)\n\n下一个问题：\n\n坐标轴和度量值显示同时切换。\n\n方法也很简单，只要再创建一个字段参数，写几个度量值，拖进字段参数中：\n\n![image-20220517150755533](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150755533.png)\n\n\n\n自动添加到页面一个切片器：\n\n![image-20220517150803492](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150803492.png)\n\n\n\n这样x轴和y轴都是参数：\n\n![image-20220517150809781](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150809781.png)\n\n\n\n随意切换两个参数即可达到想要的动态变换：\n\n![2](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/2.gif)\n\n\n\n\n\n限制：\n\n- AI与Q&A可视化对象不适用\n- 不能在一个参数中同时选择多个值，或者不选，这很正常，不用解释\n- 不能使用隐式度量值\n\n\n\n发挥想象的时刻来了！你都会用它来做出哪些神奇的可视化对象呢？请在留言区留下你的ideas吧\n\n![图片](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/640)\n"
  },
  {
    "path": "_posts/2022-05-17-Power BI【字段参数】更多细节说明.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-parameter.jpg\ncatalog: true\ntags:\n    - PowerBI\n---\n\n昨日对刚刚更新的字段参数进行了一些说明，朋友们普遍表示很有价值：\n\n[它来了，它来了，Power BI的5月更新带着【字段参数】向你走来了](http://mp.weixin.qq.com/s?__biz=MzI2MDY3NDk1OA==&mid=2247489532&idx=1&sn=ea8937bc5ed49792b3d246199aebdee8&chksm=ea67531ddd10da0bff6732b2964177fdb4cb3a6d05659e74a0070ffaa82037fd5eec7191044e&scene=21#wechat_redirect)\n\n同时也提出了一些细节的问题，今天来说明一下：\n\n#### 1、不同的字段是否可以设置不同的格式吗？\n\n是！\n\n![4](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/4.gif)\n\n\n\n有了字段参数，各个度量值字段之前完全是独立的，这尊重了度量值的复用原则，独立原则，不必再像计算组那样再单独设置以便格式。\n\n#### 2、设置完的参数如何修改呢，比如添加新的选项？\n\n这个问题好办，实际上参数设置完成之后，会自动生成一个公式：\n\n![image-20220517152051739](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517152051739.png)\n\n按照公式的写法，我们只要在后面继续添加一行即可：\n\n![image-20220517152236750](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517152236750.png)\n\n同样的道理，不想要某个字段了，我们也可以对其进行删除。\n\n\n\n#### 3、参数度量值可以排序吗？\n\n可以！\n\n默认它是按照拖放字段的先后顺序的：\n\n![image-20220517150958182](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517150958182.png)\n\n\n\n不过，我们也可以随意调整这个顺序，修改参数后面的数字序号即可。比如我们将地区经理的序号从0改为3，那么它就排在了第三个了：\n\n![image-20220517151610822](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517151610822.png)\n\n因为参数本质上只有一列数据，因此无法使用按列排序功能！\n\n#### 4、参数可以多选吗？\n\n官方说是不能多选的，但是实际操作可以的，可以随意选择字段，并且进行下钻操作：\n\n![5](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/5.gif)\n\n这功能简直yyds！\n\n#### 5、按照子类别-地区的顺序下钻查看后，想反过来查看地区-子类别顺序的下钻，有办法吗？\n\n可以的！\n\n按照参数的选择顺序，就可以实现哪个在前哪个在后：\n\n![6](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/6.gif)\n\n你还有哪些骚操作呢？一起来讨论吧\n\n"
  },
  {
    "path": "_posts/2022-05-18-Power BI参数自动放大缩小数据集.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-bigsmall.png\ncatalog: true\ntags:\n    - PowerBI\n---\n\n前些天的文章中阐述了使用参数的改变来实现本地desktop创建模型、修改模型使用小的数据集，而云端service刷新使用大的数据集：\n\n[Power bi 以小易大 破电脑也能搞定大模型 - 学谦PowerBI (powerbipro.cn)](http://powerbipro.cn/2022/05/16/Power-BI-以小易大-破电脑也能搞定大模型/)\n\n获取的是本地文件夹中的文件。今天来说一说其他的数据来源。\n\nSharePoint，或者Onedrive for Business\n\n并且，上一篇文章中的方法，其实每次更新模型之后都需要在网页端进行修改参数，有些麻烦。因此本文也将重点说明如何让数据集自动在本地desktop中刷新小数据集，上了云之后刷新大数据集。\n\n\n\n设置过程：\n\n\n\n导航之后，每个table也就是一个文件夹中包含很多的文件：\n\n![image-20220517211446345](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517211446345.png)\n\n\n\n接下来我们的目的已经非常明确了，我们要实现的是：\n\n在本地desktop刷新时，只保留【数据表-小】；在云端service刷新时， 只保留【数据表】\n\n那么问题来了，如何实现？\n\n可以先思考一下。\n\n这里给出一个实现该目标的终极提示：\n\n本地desktop刷新与云端service刷新时有什么不一样？\n\n有没有什么函数返回结果是不同的？\n\n答案揭晓：\n\n![image-20220517213107503](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517213107503.png)\n\n对，就是时区！\n我们将这个8给提取出来：\n\n![image-20220517213737546](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517213737546.png)\n\n报告中显示：\n\n![image-20220517213813442](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517213813442.png)\n\n云端刷新一下：\n\n![image-20220517214105307](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517214105307.png)\n\n好了！\n\n我们找到了一个云端和本地刷新时的不同点了，接下来就可以通过这两个数字的不同去筛选不同的表了！\n\n添加一个自定义列，【数据表-小】对应0，【数据表】对应8：\n\n![image-20220517214305106](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517214305106.png)\n\n经过几步条件设置可以得到本地刷新时【数据表-小】对应1，【数据表】对应0，而这一数值在云端刷新时刚好反过来：\n\n![image-20220517214540227](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517214540227.png)\n\n筛选1，然后展开与合并表即可：\n\n![image-20220517214817730](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517214817730.png)\n\n\n\n本地刷新与云端刷新时两个表对应的【是否刷新】值：\n\n![image-20220517215029826](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517215029826.png)\n\n\n\n由于只有2个表2万多行，本地刷新很快：\n\n![image-20220517215319825](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517215319825.png)\n\n\n\n大数据集有270个文件，每个文件1万多行：\n\n![image-20220517215258594](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517215258594.png)\n\n\n\n最后一次刷新的时间就是云端自动刷新了大数据集，花了6分钟：\n\n![image-20220517215400509](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517215400509.png)\n\n因为数据量确实比较大：\n\n![image-20220517215452794](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220517215452794.png)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/2022-05-24-Power BI 无限刷新-内部指导流程.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-bigsmall.png\ncatalog: true\ntags:\n    - PowerBI\n---\n\n\n\n![image-20220524143346238](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524143346238.png)\n\n\n\n### 一、前提条件\n\n①账号从学谦处获取\n\n②账号级别为Pro\n\n③报告与数据集必须在新创建的工作区，而不能是个人工作区\n\n④数据集已经配置好数据网关或数据源凭证，能够打开定时刷新（确认能打开即可，不必打开）\n\n\n\n### 二、配置流程\n\n#### 1、Pro账号点击premium试用\n\n登录云端[Power BI](https://app.powerbi.com/home)，点击右上角头像-开始试用：\n\n![image-20220524131410416](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524131410416.png)\n\n\n\n出现PPU试用剩余天数即可：\n\n![image-20220524131652469](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524131652469.png)\n\n#### 2、设置高级工作区\n\n进入工作区，点击上方的设置：\n\n![image-20220524132941502](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524132941502.png)\n\n点击高级版-选择premium per user，并保存。\n\n![image-20220524133045174](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524133045174.png)\n\n\n\n#### 3、添加API模块\n\n点击此处的访问：\n\n![image-20220524135503558](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524135503558.png)\n\n在此处输入powerbiapi，会出现一个匹配的项目，点击，并添加即可：\n\n![image-20220524135721343](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524135721343.png)\n\n\n\n#### 4、找到要刷新的数据集\n\n进入数据集所在的工作区，点击想要刷新的数据集\n\n![image-20220524141517625](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524141517625.png)\n\n在数据集页面，复制地址栏的地址。\n\n![image-20220524141632657](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524141632657.png)\n\n注意，此地址一定是如下格式：\n\nhttps://app.powerbi.com/groups/xxxxxxxxxx/datasets/xxxxxxxxxxx/details\n\n而下面的几种格式都是不对的，肯定是复制错了地方：\n\nhttps://app.powerbi.com/groups/**me**/datasets/xxxxxxxxxxx/details\n\nhttps://app.powerbi.com/**datahub**/datasets/xxxxx\n\nhttps://app.powerbi.com/groups/xxxxx/**reports**/xxxxx/ReportSection\n\n\n\n#### 5、填写表单\n\n打开链接：https://forms.office.com/r/KuZyEeJtpA\n\n填写自己的账号，复制刚才获取的数据集链接，填入刷新时间间隔（大于或等于120秒，且此时间一定要大于你的数据集刷新时间，否则会出现刷新错误）\n\n![image-20220524142522673](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220524142522673.png)\n\n提交表单，即可享受报告无限刷新，任何时候打开永远是刚刚刷新的最新数据！\n"
  },
  {
    "path": "_posts/2022-05-25-Power BI 定时导出数据，新版ExecuteQuery.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-bigsmall.png\ncatalog: true\ntags:\n    - PowerBI\n---\n\n\n\n书接上文：\n\nhttps://mp.weixin.qq.com/s/PTp1ZN1D5GptzwaXdOoSAQ\n\n之前其实谈到过这个execute query，只不过放了一张图，大家纷纷表示不明觉厉：\n\n![图片](https://mmbiz.qpic.cn/mmbiz_png/OyXiackVTfOjNLOoWLjia8diclOTzibNJEvic88snrmJUBbzacGkg2iaA3I4uun7Urqfm3DnyzrhJKyVqD4PLp6cmTEA/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1)\n\n\n\n因为一旦涉及到API获取token这些，就会让人觉得深不可测。并且写查询的json也比较麻烦。且之前的版本还有工作区的限制。\n\n现在好了，官方直接给出了power automate的链接器，根本用不到任何的API设置与配置，查询也只要从evaluate开始写就行，就这一步：\n\n![image-20220526125715583](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526125715583.png)\n\n输出：\n\n![image-20220526125815330](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526125815330.png)\n\n与 Power BI 报告中一致：\n\n![image-20220526130037543](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526130037543.png)\n\n\n\n下一步自然是输出了，添加一个创建CSV表格，选择first table rows：\n\n![image-20220526130405909](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526130405909.png)\n\n\n\n写入ODB文件：\n\n![image-20220526130911734](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526130911734.png)\n\n几秒种后在ODB中就会出现这个创建的文件：\n\n![image-20220526130947286](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526130947286.png)\n\n打开发现乱码：\n\n![image-20220526131011070](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526131011070.png)\n\n这个问题我们之前谈论过的：\n\nhttps://mp.weixin.qq.com/s/PTp1ZN1D5GptzwaXdOoSAQ\n\n现在有了更简单的办法。在创建CSV表格之后添加这么一个编辑，写入以下内容：\n\n```\nconcat(uriComponentTostring('%EF%BB%BF'),body('创建_CSV_表格'))\n```\n\n这段代码的本质是在写入csv的时候添加文件头，以识别中文。\n\n![image-20220526131418966](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526131418966.png)\n\n\n\n再次运行后，打开新建的文件，正常：\n\n![image-20220526131337294](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526131337294.png)\n\n\n\n接下来我们就可以将手动触发改为重复周期，固定时间间隔下载一个文件：\n\n![image-20220526131638592](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526131638592.png)\n\n然后在ODB中就会有一系列的文件生成：\n\n![image-20220526133557089](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526133557089.png)\n\n\n\n正如前文所说的这个案例：\n\n团队正在使用一个项目开发进度的软件（如Trello或Teambition ），记录着每一个子公司每一个项目的开展进度，每天软件自动或者项目管理人员手动更新进度，比如昨天是32%，今天是35%。\n\nPower BI可以通过API获取这些数据，但是这些数据永远是最新的，而之前的进度就没有了。\n\n那么如何获取每天的进度趋势，以为将来的分析需要呢？\n\n\n\n使用execute query正好可以完美解决。\n\n如果再配合“向数据集添加行”，可以实现什么呢？\n\n![image-20220526132242920](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526132242920.png)\n\n没错，数据回写！\n\nPower BI 本身可以配置数据预警，但是配置过程相对比较麻烦，现在可以使用Power Automate运行execute query定时查询来解决。对于DirectQuery是很合适的。\n\n对于按需刷新，或者定时刷新、异步刷新的报告来说，如果配置了API，可以在每次报告刷新结束后进行execute query检查，并立刻通知最新数据。\n\n甚至execute query支持RLS，每个人只能获取属于自己权限内的数据。\n\n甚至搭配powerapps实现云端报告手写度量值，实时在报表中返回想要的结果，而根本无需使用 Power BI Desktop 来操作，也就意味着MacBook可以间接实现度量值的编写。\n\n好了，今天的文章就到这里了。\n\n如果你还有什么奇思妙想，欢迎在留言区讨论。\n"
  },
  {
    "path": "_posts/2022-05-xx-Power BI execute query.md",
    "content": "---\nlayout:     post\nheader-img: img/post-bg-bigsmall.png\ncatalog: true\ntags:\n    - PowerBI\n---\n\n\n\n\n\n这是你的工作区：\n\n![image-20220526114254770](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526114254770.png)\n\n这是我的工作区：\n\n![image-20220526114142646](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220526114142646.png)\n\n\n\n"
  },
  {
    "path": "_posts/2022-06-xx-GitHub Copilot 即将收费？有这钱干点啥不好.md",
    "content": "GitHub Copilot 即将收费?有这钱干点啥不好\n\n### 概要\n\n从商业上来讲，这么费钱的项目，终究要走上收费的道路。虽说比尔盖茨将99.9%的资产都捐了（自然是为了完美避税），可微软又不是什么慈善公司，真以为它是用爱发电那可真是太天真了。\n\n\n\n### 什么是Copilot \n\n这是一款可以帮助程序员写代码的AI，可以根据你写的代码或者注释、函数名或上下文语义，自动补全代码，供程序员参考。之前做过一个视频介绍，这里就不详细解释了，感兴趣的可以看看下面的视频。\n\n[AI帮你写代码，程序员要失业了？ (zhihu.com)](https://www.zhihu.com/zvideo/1440308104424108032)\n\n\n\n### 马上面临收费\n\n曾经免费了一年时间，再有2个月就要收费了。\n\n说实话这玩意是真方便，现在写个简单代码就是写个函数名写点注释，然后等着副驾驶copilot跳出来，按个tab就ok了。这种写代码的丝滑感受，不用github copilot真的难以体会到。\n\n这种AI不是让你丧失写代码的能力，而是帮你省去了很多ctrl+c和ctrl+v和帮你写一些你得花时间但是不太需要走脑子的那种代码。\n\n因此很多人表示，用了就离不开，就算收费我也给！\n\n不过一个月10刀60多RMB，一年120刀合计700多RMB还是有点小疼的。\n\n![image-20220623200734821](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623200734821.png)\n\n### 学生和开源维护者免费\n\n想要免费？可以！\n\n只要你对开源社区有比较重要的贡献（由Github来评定）或者你能通过学生验证（注意，此处用的是通过学生验证，而不是仅限于学生），就可以免费来使用，并且Github学生认证还有一众数不胜数的其他优惠。\n\n![image-20220623202011597](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623202011597.png)\n\n![image-20220623202025365](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623202025365.png)\n\n\n\n因此，想要继续免费使用copilot，要么你需要持续对github社区做出贡献，有较好的开源项目；要么你需要通过github的学生认证。\n\n而通过学生认证这件事，我是在行的。\n\n### Github学生认证\n\n当前，国内大部分高校的邮箱和学籍信息是无法通过github学生认证的，基本上都是秒拒或者一直pending然后拒绝。\n\n什么原因大家也都很清楚，有一段时间，全球通过github学生认证的账号有60%来自于神秘的东方大国。直接导致github和一众伙伴被薅疼了。\n\n当前，学生认证这种事，还是需要特殊渠道来办的。\n\n![image-20220623203426853](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623203426853.png)\n\n![image-20220623203433060](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623203433060.png)\n\n![image-20220623203438594](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623203438594.png)\n\n![image-20220623203444690](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220623203444690.png)\n\n\n\nGithub学生认证一次持续2年以上，在原有的账号进行认证，300元/次，需要提供github账号和密码。\n\nGithub学生账号年付费制度，100元/年，无需提供任何材料，直接发放教育邮箱和认证过的github学生账号（邮箱和账号独有）。\n\n如有需要，添加学谦微信：powerxueqian，暗号github"
  },
  {
    "path": "_posts/2022-06-xx-Github学生包申请流程.md",
    "content": "先说结论，无论你是国内的还是国外的，无论你是哪个大学，无论你有没有学校邮箱，无论你有没有学生证明材料，无论你现在还是不是学生。\n\n只要你想要Github学生包，我都可以给你搞到。\n\nhttps://forms.office.com/r/fRfbkUzkLn\n\nwx：powerxueqian\nq群：920303542\n\n\n\n### 一、首先你得有一个github账号\n\n#### 1、打开github官网输入邮箱\n\n[GitHub: Where the world builds software · GitHub](https://github.com/)\n\n![image-20220612175123025](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612175123025.png)\n\n输入任何一个邮箱都可以，前几年是输入edu.cn邮箱秒批的，但是近些年因为国内教育邮箱滥用，就不行了。\n\n#### 2、填入相关账号信息，设置密码并验证\n\n![image-20220612175453205](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612175453205.png)\n\n#### 3、邮箱中获取验证码，绑定邮箱：\n\n![image-20220612175558413](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612175558413.png)\n\n#### 4、选择student\n\n![image-20220612175627727](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612175627727.png)\n\n#### 5、选择自己的喜好，根据情况填一下即可\n\n![image-20220612175647693](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612175647693.png)\n\n\n\n#### 6、注册好啦！\n\n![image-20220612175830317](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612175830317.png)\n\n### 二、申请education  package\n\n先说结论，无论你是国内的还是国外的，无论你是哪个大学，无论你有没有学校邮箱，无论你有没有学生证明材料，无论你现在还是不是学生。\n\n只要你想要github学生包，我都可以给你搞到。\n\n订阅制，年付费。\n\n#### 1、打开education申请页面：\n\n[Engaged students are the result of using real-world tools - GitHub Education](https://education.github.com/)\n\n点击join global campus\n\n![image-20220612180108907](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612180108907.png)\n\n\n\n新的页面确认13周岁\n\n![image-20220612181250219](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612181250219.png)\n\n导航到资料填写页面\n\n![image-20220612181202764](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612181202764.png)\n\n\n\n#### 2、资料填写\n\n选择要申请的邮箱，输入学校名，申请计划认真填写，用英文，表达自己想要用Github实现什么\n\n![image-20220612181355057](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612181355057.png)\n\n#### 3、上传证明文件\n\n相关资料进行拍照上传，有些学校的学生证不认，直接秒拒。\n\n![image-20220612181616701](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612181616701.png)\n\n\n\n有的学校的这里不会显示upload an image，那就只能用手机拍照上传，注意拍照的时候因为不会有预览，只是黑框，手别抖。\n\n![image-20220612181735177](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612181735177.png)\n\n提交完资料页面会显示等待时间，可长可短。\n\n![image-20220612181958899](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612181958899.png)\n\n同时邮箱中也会收到邮件，4天内确认。其实用不了那么长时间， 快的话，半个小时就收到确认邮件了。\n\n![image-20220612182338403](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612182338403.png)\n\n\n\n此时打开页面会显示pending\n\n![image-20220612182246638](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612182246638.png)\n\n#### 4、收到欢迎邮件\n\n大约几分钟之后就收到了欢迎邮件，收到这个邮件就表示通过了\n\n![image-20220612184234400](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612184234400.png)\n\n\n\n去github中查看状态，也是approved\n\n![image-20220612184206935](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612184206935.png)\n\n#### 5、畅游github学生包的世界！\n\n上百种优惠等你来领取\n\n![image-20220612184629270](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220612184629270.png)\n\n\n\n无论你是国内的还是国外的，无论你是哪个大学，无论你有没有学校邮箱，无论你有没有学生证明材料，无论你现在还是不是学生。\n\n只要你想要Github学生包，我都可以给你搞到。\n\nhttps://forms.office.com/r/fRfbkUzkLn\n\n填表后根据要求操作即可获取资料。\n\nwx：powerxueqian\nq群：920303542"
  },
  {
    "path": "_posts/2022-06-xx-PowerBI注册账号申请.md",
    "content": "## **一、超过万人注册成功**\n\n很幸运，你看到了这篇文章，powerbi账号终于可以轻松拿到手了。\n\n自己注册powerbi已经很难了，我们直接给你账号+密码，你需要的只有登录改密码，将它变成你自己的账号！\n\n账号获取链接：https://forms.office.com/r/mYuZfF7sUH\n\n注意，以上链接已经注册了超过万人（2023.5.1数据）！\n\n![img](https://pic3.zhimg.com/80/v2-8de69ac1c3a92c1e49550f1aaba4e776_720w.jpg)\n\n\n\n![img](https://pic2.zhimg.com/80/v2-a8f86a617149b842474e8e5910e52812_720w.jpg)\n\n\n\n![img](https://pic1.zhimg.com/80/v2-5eea357ef936e4f5e0cfdb879edfad5f_720w.jpg)\n\n\n\n\n\n\n\n## **二、填表，输入想要的账号类型和用户名**\n\n我们提供了基础的域名，如用户名：abc@powerbi.onmicrosoft.com\n\n按照要求填表提交即可。\n\n我们也提供了如[xueqian@powerbipro.cc](mailto:xueqian@powerbipro.cc)这样的高端域名甚至@powerbi.bi这样的顶级域名，填表链接：\n\n**https://forms.office.com/r/bicCwcmUDe**\n\n\n\n## **三、获取密码**\n\n填表说明上清晰明了。请仔细查看并注意选择正确的选项。\n\n\n\n## **四、激活账号并使用**\n\n#### 1、浏览器中输入登录网址：[https://login.microsoftonline.com](https://login.microsoftonline.com/)，登录窗口，输入获取的账号\n\n![img](https://pic4.zhimg.com/80/v2-d3350ad7d737f57b88872fdfd3c3803a_720w.jpg)\n\n\n\n#### 2、填入密码并登录。\n\n![img](https://pic3.zhimg.com/80/v2-4d75440c2e4e9a5af6952d98ac29ccc1_720w.jpg)\n\n\n\n#### 3、更新密码，为了您的账户安全，请务必修改密码后妥善保存密码，谨防丢失或泄露。\n\n新密码要求必须包含大小写字母和数字。\n\n![img](https://pic4.zhimg.com/80/v2-2c13f247f8a5b72534630b472d850ece_720w.jpg)\n\n\n\n\n\n如果出现了下面的画面，可以点击下一步，然后绑定自己的手机和authenticator，一切都是为了自己的账号安全\n\n![img](https://pic4.zhimg.com/80/v2-50b85be504fc67d9b5319283f8251069_720w.jpg)\n\n\n\n也可以点击试用其他账户，它会返回一个页面：\n\n![img](https://pic3.zhimg.com/80/v2-1618959048c5c898aade1fd6e6cc27df_720w.jpg)\n\n\n\n\n\n点击登录即可。\n\n#### 4、打开powerbi主页，点击左上角的9个点，选择powerbi即可进入主页。\n\n![img](https://pic3.zhimg.com/80/v2-b9626c1b4d78c5bc9a743d4d67732811_720w.jpg)\n\n\n\n\n\n\n\n![img](https://pic4.zhimg.com/80/v2-638a3cf52212a025d9976719c2fe8656_720w.jpg)\n\n\n\n\n\n![img](https://pic4.zhimg.com/80/v2-19b4671014d9d60392c43a9f9ba832cb_720w.jpg)\n\n\n\n账号注册完毕！\n\n#### 5、登录Power BI desktop，就可以使用软件制作power bi报告并发布到云端了！\n\n![img](https://pic2.zhimg.com/80/v2-913ec66a228f8fff7bb246365170cdda_720w.jpg)\n\n## OK！整个过程结束！\n\n登录过程可能会遇到各种问题，没关系，随同账号密码一起发送的，还有详细的登录步骤，你能遇到的几乎任何一个问题，我们都已经给出了详细的解决方案。\n\n"
  },
  {
    "path": "_posts/2022-06-xx-为什么玩转Power BI一定需要Office 365.md",
    "content": "为什么玩转 Power BI 一定需要 Office 365？\n\nBI工具数不胜数，Power BI、Tableau、FineBI、永洪BI、百度智能云等，甚至 python、MATLAB 都可以实现报表功能。\n\n但是为什么 Power BI 能连续15年稳坐 Gartner 魔力象限头把交椅？\n\n![img](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/v2-8e418f97e15d674c07d8564c58839a8a_720w.jpg)\n\n\n\n诸多原因。\n\n今天想要阐述的一个观点是：\n\n**正是因为有了 Office 365 这个强大的后盾， Power BI 才会像今天这样受到这么多人的推崇。**\n\n本文主要从以下几个方面阐述：\n\n1、Power BI 从 Onedrive for Business获取文件或文件夹\n\n2、Power BI pbix文件直接从 Onedrive for Business获取并同步\n\n3、Power BI 官方支持嵌入PPT，助力你成为数据演说家\n\n4、Power BI 拥有 Power Automate 的强大加持\n\n\n\n## 一、Power BI 从 Onedrive for Business获取文件或文件夹\n\n![image-20220616184636237](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616184636237.png)\n\n几乎所有的教程都会告诉你，使用 Power BI 获取数据最简单的方式是从本地excel表中获取，紧接着教你如何从本地文件夹中获取多个文件。\n\n从这一习惯养成的第一天开始，你就为将来某一天的后悔埋下了祸根。\n\n因为不需要太久之后，你就要面临数据刷新、定时刷新、网关配置的问题，此时，如果你看到下面的每一个都需要进行一次凭据的选择：\n\n![image-20220616192831564](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616192831564.png)\n\n\n\n请问，你作何感想？\n\n尤其是当你每次在本地添加一个新的excel表，保存，发布，等待其自动刷新，但是总是不刷新，找了一圈原因，到数据集这里一看，哦对，需要对新添加的这张表设置凭据。你会不会懊恼不已？\n\n尤其是当你关闭了电脑，你会发现无论如何你是没有办法得到最新的数据的。因为从本地文件获取数据，必须通过网关，而网关必须在开机运行的电脑上。\n\n本地网络出现故障或者网速不给力的情况下，刷新报告是非常漫长的过程，而且经常会报错。\n\n那么，Office 365 可以带给我们什么呢？\n\n使用从 Onedrive for Business 获取文件我们有以下的优势：\n\n![image-20220616151443102](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616151443102.png)\n\n\n\n①无需手动配置网关。因为数据是云对云，因此本地是否开机，是否联网，网络是否通畅，网速是不是给力毫无影响。而且因为数据都是走的微软服务器，效率也会加倍。\n\n注意：这里使用的是 Onedrive for Business（ODB） ，而不是个人版或家庭版的 Onedrive（OD），因为OD本质上只是个web url，具体参考这篇文章：[【重磅】PowerBI从Onedrive个人版获取文件](https://mp.weixin.qq.com/s/kUuUrhflgYmVYg6KgnToTg)。这里的从ODB获取数据不要求Power BI和ODB为同一个账号或同一个组织账号，但是世纪互联版的 Power BI 无法获取国际版的ODB文件，反之亦然。（特别注意，你可能会登录国际版 Power BI 在本地desktop中尝试获取世纪互联的 ODB 文件成功，但是云端却是无法刷新的，这一点要注意，参考这篇文章的末尾的说明：[针对“PowerBI从Onedrive获取文件”两篇文章做个补充](https://mp.weixin.qq.com/s/TG98cJ5qUQ77ophWU1vEAA)）\n\n②不论从该 ODB 中获取多少个文件或者文件夹，数据源凭证这里永远只有一个，也就说，你只需要在第一次发布报告时配置好，那么以后任何时候再次发布报告，哪怕是发布其他的报告，也都无需再次配置凭据。\n\n③因为有很多公司可能会团队共同维护数据，需要设置共享盘，那么ODB就是一个绝佳的选择，单个用户5T的空间，想必任何数据都可以满足要求。同一个组织内的用户之间通过共享文件和文件夹的方式进行配合实现组织的高效运转。\n\n当然，其实如果你的数据量比较大，还是建议上数据库的。\n\n④ODB中的数据文件，误删或者修改全部都有记录，全都可以随时找回，再也不必担心数据丢失问题。\n\n\n\n## 二、Power BI pbix 文件直接从 Onedrive for Business 获取并同步\n\n几乎所有的教程都会告诉你，desktop上制作报告完毕之后，点击发布即可上传到service。如果修改报告，那么只要修改完成后再次点击发布，覆盖原来的云端报告即可。\n\n这很好。但仅限于你没有 Office 365 中的 ODB。\n\n如果你有，那么恭喜你，你解锁了一个**全新的发布报告方式**。\n\n微软官方原话：\n\n[从 Power BI Desktop 文件获取数据 - Power BI | Microsoft Docs](https://docs.microsoft.com/zh-cn/power-bi/connect-data/service-desktop-files)\n\n**OneDrive - 企业** – 如果你有 OneDrive for Business，并且使用登录 Power BI 的同一帐户登录到其中，**这是将 Power BI Desktop 中的工作与你在 Power BI 中的数据集、报表和仪表板保持同步的有史以来最有效的方法**。由于 Power BI 和 OneDrive 都位于云中，Power BI 大约每小时会连接你在 OneDrive 上的文件一次。 如果发现任何更改，你的数据集、报表和仪表板会在 Power BI 中自动更新。\n\n\n\n![图片](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/640)\n\n图片来源：PowerBI战友联盟\n\n\n\n操作方式：\n\n#### 1、云端工作区中，点击新建-上传文件：\n\n![image-20220616190946094](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616190946094.png)\n\n#### 2、点击OneDrive企业版：\n\n![image-20220616191217548](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616191217548.png)\n\n\n\n#### 3、点击其中的pbix文件：\n\n![image-20220616191311438](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616191311438.png)\n\n#### 4、点击右上角连接，即可同时建立数据集、报表和仪表板：\n\n![image-20220616191408860](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616191408860.png)\n\n\n\n#### 5、点击数据集的三个点-设置：\n\n![image-20220616191553395](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616191553395.png)\n\n你的数据集设置里有这个吗？\n\n![image-20220616191651798](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616191651798.png)\n\n\n\n默认情况下，OneDrive 每小时更新一次文件。\n\n你还在为每天8次定时刷新不知道该怎么设置而苦恼吗？\n\n有了 OneDrive 的24次加持，你还有什么可担心的呢？\n\n#### 6、更新数据\n\n本地修改了文件之后，需要做什么吗？\n\n不需要做任何事情，这一切，交给 OneDrive 和 Power BI 去做了。一小时更新一次，等待即可。\n\n如果不想等？点击“立即刷新”：\n\n![image-20220616192533981](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616192533981.png)\n\n你就可以得到最新的模型和数据。\n\n#### 7、团队玩法与个人玩法的不同\n\n你的工作区右侧三个点，里面有**文件**这个选项吗？一定是没有的！\n\n![image-20220616193322361](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616193322361.png)\n\n\n\n这只是团队玩法和个人玩法之间的数不胜数的差异之间的微不足道的一个。\n\n更多企业级应用分享，请持续关注学谦公众号。企业级应用配置，直接联系学谦沟通。\n\n#### 8、小结\n\n从ODB中获取pbix文件的优势不仅限于如此，假如你有多个工作区，都使用同一个pbix文件中的数据集，进而各自开发报告，如果你是总数据集的持有者，请问你是会选择对同一个pbix文件修改之后在多个工作区分别覆盖发布，还是修改之后点击保存关闭，喝个下午茶，等待系统系统更新？想必你一定会做出正确的选择！\n\n甚至更进一步讲，我们可以通过某种设置，在本地报告修改保存关闭后，自动更新数据集，第四部分中将要介绍的Power Automate就可以完美地实现这个功能。\n\n\n\n## 三、Power BI 官方支持嵌入PPT，助力你成为数据演说家\n\n官方的说法是：让数据讲故事。\n\n在以往，你可能会选择 Power BI Tiles或者Web Viewer，但都非官方，支持也一般。\n\n现在，你有了一个全新的选择：\n\n#### 1、从PowerPoint中获取 Power BI 插件：\n\n![image-20220616195549669](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616195549669.png)\n\n\n\n![image-20220616195625042](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616195625042.png)\n\n\n\n![image-20220616195651755](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616195651755.png)\n\n#### 2、在Power BI service中获取链接：\n\n①②③看步骤：\n\n![image-20220616200959834](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616200959834.png)\n\n点击复制：\n\n![image-20220616201031411](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616201031411.png)\n\n\n\n#### 3、粘贴到ppt中：\n\n![image-20220616201841957](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616201841957.png)\n\n#### 4、呈现结果：\n\n![image-20220616201912410](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616201912410.png)\n\n\n\n\n\n#### 5、小结\n\n在ppt中嵌入power bi可以实现真正意义上的用数据讲故事。powerbi嵌入PPT不仅仅是powerbi的革命，同时也是ppt的革命。\n\n很多人不清楚powerbi和ppt到底是什么关系，其实把ppt的全称写出来就懂了：\nPowerPoint\nPowerBI\n\n这关系比作王中军和王中磊不过分吧。\n\n**前提：登录powerbi的账号和登录office365的账号必须是同一个账号或者同一个组织下的。**\n\n\n\n## 四、Power BI 拥有 Power Automate 的强大加持\n\n关于 Power Automate 和 Power BI 的强大联合应用，之前也说过不少了，大家可以直接参考下列的文章：\n\n\n\n基础的flow是免费的的，但是高级账号还是需要 Office 365 的加持。\n\n\n\n\n\n## 总结\n\n总之， Power BI 是强大的，然而在 Office 365 的加持之下，它，更加强大！\n\n你需要有和你的 Power BI 账号是同一个账号或者同一个域名下的 Office 365 企业级权限账号来实现这一切。\n\n至于价格：\n\n![image-20220616205056758](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220616205056758.png)\n\n\n\n可以考虑完全使用官网的正版。也可以联系学谦搞定这一切。"
  },
  {
    "path": "_posts/2022-06-xx-增强刷新.md",
    "content": "\n\n\n\n\n\n\n\n![image-20220615133423322](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220615133423322.png)\n\n\n\n![image-20220615133613119](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220615133613119.png)\n\n\n\n![image-20220615133505988](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220615133505988.png)"
  },
  {
    "path": "_posts/2022-07-18-Power BI从Dataverse获取数据.md",
    "content": "不知从何时起，它已经在这里很长时间了。\n\n![image-20220718141608296](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718141608296.png)\n\n\n\n这么重要的位置，其重要性可见一斑。\n\n或者说，至少微软想让其变得重要。\n\n![包含 Microsoft Power Platform 概述的图表。](https://docs.microsoft.com/zh-cn/power-apps/maker/data-platform/media/data-platform-cds-intro/platform.png)\n\nPower Platform包含的5大组件，全都需要数据作为粮食投喂。\n\n而数据来源，上图提供了3个。\n\n数据连接器：通过各式各样的链接器，链接来自不同数据源的各式数据。这是打通与第三方世界数据的壁垒。\n\nAI：这是未来发展趋势，AI人工智能获取那些非结构化的模型以得到数据。\n\nDataverse：数据存储的元宇宙。不仅仅是个数据库。\n\n\n\n熟悉SharePoint的，几乎都会用过list，这是管理文档和一些简单数据列表比较好的系统。然而创建一些表之间关系或者一些基于对象的数据时就无能为力了。Access目前已经很少有人在用。SQL server虽然安全性和处理关系型数据的能力强大，但是毕竟想要驾驭SQL需要深厚的技术能力。\n\n于是Dataverse出现了。\n\n关于Dataverse的具体来历、功能如何强大、如何建立表和表之间的关系，我们暂且按下不表。\n\n今天只来说一说从Power BI中如何获取Dataverse里的数据，以及想要使用Dataverse需要的条件。\n\n1、点击Power BI主页上的“数据”工作区的Dataverse：\n\n![image-20220718141608296](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718141608296.png)\n\n当然，前提是你已经有了Power BI账号，并且已经有了Dataverse数据表。（别急，后面会说）\n\n2、选择想要导入的表格，勾选并加载\n\n![image-20220718155716774](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718155716774.png)\n\n3、选择数据连接模式\n\n建议直接选择DirectQuery直连模式，为方便以后我们的数据修改与获取操作。\n\n![image-20220718155817535](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718155817535.png)\n\n4、选择合适的列进行可视化呈图\n\n![image-20220718160633428](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718160633428.png)\n\n整个过程其实非常简单。\n而且一旦数据进入到模型，剩下的建模工作都完全一致了。\n\n我们可以使用dataverse数据的实时链接特性在报告中插入powerapps可视化对象来实现数据的实时联动更新：\n\n\n\n关键是Dataverse的数据在哪里创建，接下来我们来说这个问题。\n\n1、首先你得有一个Power BI账号，并且得能用Power Apps\n\n\n\n2、打开Power Apps，选择“表”：\n\n![image-20220718163045851](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718163045851.png)\n\n3、点击新建表：\n\n\n\n![image-20220718163148429](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718163148429.png)\n\n或者你也可以选择导入表\n\n![image-20220718163745782](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718163745782.png)\n\n4、如果选择了新建表，可以设置表的属性及主列\n\n注意显示名为英文或数字\n\n![image-20220718164331042](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718164331042.png)\n\n5、添加列和数据\n\n注意列名也需要为英文或数字；并且可以提前设置好数据类型\n\n![image-20220718164733065](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718164733065.png)\n\n我们也可以使用其内置的数据，比如创建者和日期、修改者和日期等。\n\n![image-20220718165406379](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718165406379.png)\n\n然后我们可以输入一些数据。随时输入和修改，随时自动保存的。\n\n![image-20220718165553263](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718165553263.png)\n\n当然，我们也可以根据此数据创建一个power apps应用，来达到数据的实时操作更新的目的。\n\n\n\n甚至，我们可以继续发挥想象，使用power automate，结合power bi最新的execute query去实现一些power bi报告中某些特定的时间节点的记录回写，甚至改写。\n\n对于power bi报告的评论系统我们现在可以有更好的解决方案。\n\n总之，发挥想象就好了。\n\n![image-20220718174252796](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718174252796.png)\n\n只不过，需要使用这些功能，需要高级版账号才可以。\n\n![image-20220718174708767](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220718174708767.png)"
  },
  {
    "path": "_posts/2022-07-18-恢复删除的flow.md",
    "content": "\n\n比如我有一个名为“测试-删除恢复”的flow\n\n![image-20220721162200604](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220721162200604.png)\n\n将其删除\n\n![image-20220721162523438](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220721162523438.png)\n\n\n\n![image-20220721162630889](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220721162630889.png)\n\n![image-20220721162857705](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220721162857705.png)\n\n\n\n![image-20220721162744197](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220721162744197.png)\n\n\n\n\n\n"
  },
  {
    "path": "_posts/2022-07-22-几个Power Automate技巧送给你.md",
    "content": "几个Power Automate技巧送给你\n\n\n\n1.停止循环\n\n\n\n2.\n\n\n\n"
  },
  {
    "path": "_posts/2022-07-22-打破不同组织间的壁垒，Power Automate同步PowerBI报告.md",
    "content": "### 背景\n\n学谦数据集团下设多个部门，其中财务部和运营部的小伙伴先后独立购买了全球版的Power BI pro账号10个和15个各自开发报告（都怪学谦疏于管理）。由于是分开独立购买，因此域名自然是不同的，即分属两个不同的组织。\n\n财务部报表开发得差不多了的时候，为我提供了一个账号，这样我就可以随时查看当前的各项指标数据并监督查看他们的开发进度。\n\n后来，运营部的小伙伴，也给我提供了一个账号。\n\n那么，我就有2个不同组织下的账号。问题就来了，我要登录财务部账号，就得退出运营部账号，反之亦然。或者我需要两个浏览器，分别登录这两个账号。\n\n![image-20220722170341156](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722170341156.png)\n\n很明显，这，不太方便。\n\n### 讨论思考\n\n首先，两个不同的组织之间想要共享报告是不可能的。\n\n而且同一个账号也不可能同时位于两个组织，工作区也无法邀请组织外的人进入。\n\n既然云端service走不通，那么只能想办法从本地的pbix文件上突破了。\n\n办法倒是有，运营部可以将pbix文件直接发给我，我在本地电脑上打开刷新浏览。但是这样问题很多：第一，我开会时或者出差带着我的MacBook，没有装虚拟机，无法打开pbix文件；第二，他们最近开发比较频繁，不可能每一个版本都给我发文件，而且发文件整体的发送保存比较麻烦不说，通过qq微信安全性也不好……\n\n忽然想起来，我们不仅购买了Power BI pro，还同时购买了全套的office365商业版，有Onedrive for Business，这样通过ODB或者SharePoint就可以同步过来文件，而且他们平时也都是使用ODB进行版本更迭与控制。但是仍然解决不了我的电脑无法安装Power BI desktop的问题。\n\n所以，本地的pbix分享所有的方式，其实都不满足我的要求。还是得在云端报告上找方法。\n\n等等，Onedrive for Business，Power BI可以直接从里面读pbix文件啊！\n\n但是有一个前提，Power BI只能从同一个账号下的ODB或同一个组织下的SharePoint中读取pbix文件。\n\n我们梳理一下手头的东西：\n\n财务部Power BI和ODB账号，运营部Power BI和ODB账号，运营部ODB中的pbix文件。\n\n不难发现如下的解决方案：\n\n只要让运营部ODB中的pbix文件出现在财务部的ODB中，这样财务部的Power BI就可以获取这个pbix文件，显示在财务部的Power BI云端工作区当中。\n\n那么问题的关键就在于：\n\n如何让运营部ODB中的pbix文件出现在财务部的ODB中？\n\n或者换个说法：\n\n如何让运营部ODB中的pbix文件能够随时同步到财务部的ODB中呢？\n\n![image-20220722170513475](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722170513475.png)\n\n### 解决方案\n\n想必大家已经猜到了， 或者说从本文的标题上也找到了答案：\n\nPower Automate\n\n![image-20220722170819269](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722170819269.png)\n\n我们可以使用云端自动化流，当运营部ODB中的pbix文件发生修改时，将该文件复制到财务部的ODB中，覆盖原有的文件以达到更新的目的。\n\n![image-20220722172112120](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722172112120.png)\n\n下图是运营部的ODB中的文件：\n\n![image-20220722172209498](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722172209498.png)\n\nflow运行成功后，在财务部的ODB中出现了同样的文件，并且每次文件更新时，财务部ODB中的文件会直接覆盖更新：\n\n![image-20220722172321949](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722172321949.png)\n\n而且进行多次更新后，我们可以在财务部ODB的文件上选择查看版本，并且随时进行版本控制。\n\n同时也说明了一个事实：在ODB中进行同名文件覆盖操作，同样会保留原来文件的版本，这一点非常重要。因为在本地计算机上进行文件的覆盖操作，你是无法找回原来文件的。\n\n![image-20220722172711826](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722172711826.png)\n\n\n\n这样，我们在财务部的Power BI service上创建一个工作区，命名为“运营部”，将财务部ODB中的“运营部2022.pbix”文件添加到工作区，就可以实现在登录同一个账号的提前下，查看两个不同组织发布的报告了。\n![image-20220722173428168](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722173428168.png)\n\n\n\n而且由于是从ODB中获取的pbix文件，同样会享受到自动的每小时模型更新。\n\n![image-20220722173806580](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722173806580.png)\n\n下图为整个的流程：\n\n![image-20220722174206856](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220722174206856.png)\n\n\n\n\n\n#### 总结\n\n本文所述案例，是真实发生在国内某企业的，由于缺乏整体宏观管理，导致同一公司的两个部门之间同时使用Power BI但却不在同一个组织内，让两个部门之间的报表和数据出现不可跨越的壁垒。\n\n本文使用Power Automate云端流解决了这一问题。为已经发生此类相似事情的企业提供方法借鉴，同时也对可能会发生此类事件的企业做出一个警示。\n\n同时，也建议公司还是对对账号进行统一管理，统一域名，方便进行同一组织下的数据分享。\n\n对于该方法，发挥一下想象，拓展一下想必还可以发挥更多更有趣也更有用的作用。\n\n我是学谦，专注于企业数字化生产力提升与完善。\n\n"
  },
  {
    "path": "_posts/2022-07-25-Power Automate表达式无法输入和修改时的处理办法.md",
    "content": "自从flow的主页改为https://make.powerautomate.com，速度是快了不少，但是好像bug也多了起来。\n\n正常而言，一个action输入框点击之后，可以在表达式的位置进行自定义添加或者修改。\n\n之前一直很正常，但是这两天突然就无法输入了：\n\n![img](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/640)\n\n试了重新登录、更换浏览器、删掉缓存、更换账号、更换网络、更换电脑，一律无法使用。\n\n可能办法真的只剩下一个了，换人。\n\n**经过一番摸索**，发现了如下的解决办法：\n\n比如我的forms表单“商品分类”中的选项格式一般为：“A、黄金叶”，“B、软中华”，我想提取顿号前边的A、B、C这些，正常我应该在表达式中直接写：\n\n```\nsplit(outputs('获取回复详细信息')?['body/rc7dxxxb5'],'、')[0]\n```\n\n但是现在没有办法在表达式中直接写，我可以在输入框中\n\n```\n@{split(outputs('获取回复详细信息')?['body/rc7dxxxb5'],'、')[0]}\n```\n\n即将原本应该写在表达式中的内容，放在@{}里面，然后直接在输入框中粘贴就可以了。\n\n![无法编辑表达式-复制修改](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%97%A0%E6%B3%95%E7%BC%96%E8%BE%91%E8%A1%A8%E8%BE%BE%E5%BC%8F-%E5%A4%8D%E5%88%B6%E4%BF%AE%E6%94%B9.gif)\n\n**又经过一番摸索**，找到了另一种办法：\n\n点击右上角的齿轮，点击“查看所有设置”\n\n![image-20220725201624797](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220725201624797.png)\n\n将此处打开，然后保存。\n\n![image-20220725201639806](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/image-20220725201639806.png)\n\n然后就又可以开心地编辑了，只不过显示方式与之前不太一样：\n\n![无法编辑表达式-高级模式](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/%E6%97%A0%E6%B3%95%E7%BC%96%E8%BE%91%E8%A1%A8%E8%BE%BE%E5%BC%8F-%E9%AB%98%E7%BA%A7%E6%A8%A1%E5%BC%8F.gif)\n\n\n\nok！"
  },
  {
    "path": "_posts/2022-08-08在Onedrive for Business中创建文件夹.md",
    "content": "# 在Onedrive for Business中创建文件夹\n\n在Onedrive for Business（以下简称ODB）中创建一个文件是非常轻松的一件事：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled.png)\n\n选择想要的路径，设置文件名，选择文件内容（文件内容大部分时候都是来自于其他action，比如邮件附件或者forms附件等，这里为了简化流程，随便写了一个）：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%201.png)\n\n点击运行，就可以在文件夹中找到这个文件：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%202.png)\n\n但是，如果我们想要创建一个文件夹呢？\n\n好像并没有这么一个action。\n\n不过，在测试的时候我们发现一个问题。如果创建文件时，输入的路径实际并不存在，那么它会自动生成这个路径。比如我们在文件夹路径的后边继续输入“/测试生成路径”：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%203.png)\n\n结果它也照样生成了这个文件，并且还为我们创建了一个新的文件夹：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%204.png)\n\n答案呼之欲出了：\n\n我们将这个a.txt文件删掉，不就达到了创建一个空文件夹的目的了吗？\n\n添加一个ODB的删除文件，选择上一步生成文件的ID：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%205.png)\n\n在ODB中查看，果然生成了一个空文件夹。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%206.png)\n\n我们再看一眼所需的时间，只需要14ms，根本忽略不计。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%207.png)\n\n结论：\n\nPower Automate flow虽然并没有给我们提供一个单独的action来实现在ODB中创建空白文件夹，但是我们通过一点小技巧就可以巧妙的实现。\n\nflow是一个逐渐强大的过程，虽然暂时还有很多的毛病，但是已经可以实现将platform和365大部分功能都协调运用起来，相信它会越来越好。\n\n"
  },
  {
    "path": "_posts/2022-09-23-Craft——制作惊人的文档.md",
    "content": "# Craft——制作惊人的文档\n\n![2f5fd5370d54dd556e2dd0817c4a314.jpg](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/2f5fd5370d54dd556e2dd0817c4a314.jpg)\n\n优雅到极致的一款文档产品，更新速度目前很快，每个月都会有比较大的功能更新，而且手机端体验完胜notion。但我认为两款产品应对的场景有些不同，notion的定位更类似于知识库的搭建，Craft则更加专注于文档编写。可以自己去感受一下，找到适合自己的产品才是最重要的。\n\nCraft已经支持离线存储+iCloud同步，支持用户自行备份。这一点相比notion还是要好一些，尤其是那些对自己的数据掌控把握要求较高的人。\n\nCraft没有数据库，这一点不如notion等其他的软件，但是并不是所有人都需要数据库的，大部分使用notion的人也都是用来作为笔记工具，而不是日程工具或者数据管理工具。\n\ncraft的宣传标语——制作惊人的文档。确实，craft制作的pdf还是非常棒的。\n\n![5483ed2f3e2f0eecfbb062ed89d3bcf.jpg](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/5483ed2f3e2f0eecfbb062ed89d3bcf.jpg)\n\n1000个block的限制是真不够，想要更多需要付费，但是年费不便宜，200多一年。不过幸好，部分学生可以申请到免费的试用。\n\n![142ec3ceb26f228236ed6dd4501b043.jpg](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/142ec3ceb26f228236ed6dd4501b043.jpg)\n\n![75f736b3ce19b237bfd80ec65e86eb7.jpg](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/75f736b3ce19b237bfd80ec65e86eb7.jpg)"
  },
  {
    "path": "_posts/2022-09-23-Microsoft Loop初见.md",
    "content": "# Microsoft Loop初见\n\n官网放出了链接：[https://dev.loop.microsoft.com/](https://dev.loop.microsoft.com/)\n\n目前个人版账号登录显示尚未开始：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled.png)\n\n而使用商业版账号登录已经有了内部的简单布局：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%201.png)\n\n中间的大字“无工作区”总是在提醒我们需要去创建一个工作区，很明显，右上的那个加号就是做这个的，点开之后，显示了“新建工作区”的对话框，\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%202.png)\n\n我们输入一个名字，点击创建：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%203.png)\n\n但是发现后续就没反应了：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%204.png)\n\n去后台看看什么问题：\n\n```powershell\n{\"code\":\"CreateWorkspaceGroupBlocked\",\"message\":\"Workspace Groups are not enabled in this environment\"}\n```\n\n问题是：在此环境下工作区无法创建。\n\n看来目前微软还并没有完全放开权限。\n\n具体每个用户能创建多少个，也没有说明，我们只有期待。\n\n我们再来看看右上角，首先点击用户，只有一个登出：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%205.png)\n\n点击左侧三个点，只有设置和关于：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%206.png)\n\n设置里面只有一个“通用”来设置主题颜色和字体大小：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%207.png)\n\n点击“关于”：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%208.png)\n\n整个界面现在还并没有什么有效的信息，也无法实现任何的作为笔记工具的操作。\n\n让我们一起继续期待loop的功能！\n\n最后，需要提一点。虽然loop还没有出来，但是，我已经作为全网第一个为loop付费的用户了。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled.jpeg)\n\n为了升级这个满500人的群，我开通了年费会员，成功升级为2000人群。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%209.png)\n\n这下舒坦了。"
  },
  {
    "path": "_posts/2023-07-01-限量20套！这样的Power BI管理员，你值得拥有！.md",
    "content": "# 限量20套！这样的Power BI管理员，你值得拥有！\n\n限量20套！这样的Power BI管理员，你值得拥有！\n\n**适用场景**\n\n一个报告开发人员，众多的报告查看人员。\n\n企业内安全分享，而不是通过公开web分享，所有人都可以看到报告，企业数据泄密。\n\n需要对用户进行动态管理，分权限查看报告。\n\n**内容**\n\n1、一个永久Power BI Pro许可+无限个Power BI free许可\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled.png)\n\n2、全局管理员\n\n可以任意创建、删除无限个账号；新创建账号可以与已删除账号保持一致\n\n可以对用户进行角色管理；创建一个用户A并将其设置为用户管理员，则A可以对用户进行增删改查\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%201.png)\n\n3、每一个账号都可以点击试用60天的Pro+premium+fabric\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%202.png)\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%203.png)\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%204.png)\n\n**用法**\n\n永久Pro账号用来创建工作区并发布报告；\n\n邀请基础账号进工作区并设置为不同的角色（报告发布者、成员、查看者）\n\n基础账号点击试用（60天）进入工作区，用来查看报告；\n\n60天后\n\n管理员需要进行的操作：\n\n将基础用户A从工作区删除、删除基础账号A、创建新的基础账号B、将基础账号B添加到工作区\n\n基础用户需要进行的操作：\n\n使用新的临时密码登录并改密，点击试用60天，进入工作区查看报告\n\n**优点**\n\n完全独立自主的全局管理员，只受微软管理。\n\n对其他用户实现完全管理。\n\n永久的Power BI Pro许可。\n\n无限个可以试用Power BI Pro+premium+fabric的基础账号。\n\n可以绑定自己的域名，自定义账号。\n\n**缺点**\n\n管理员每60天需要删除重建账号，用户每60天需要重新登录改密点击试用。\n\n**缺点改进**\n\n可以付费开发自动化批量删除并重建账号同时直接邀请进工作区，后台自动运行。\n\n即作为管理员，创建完账号之后，不需要任何其他额外操作。\n\n甚至用户也一直是使用同一个账号，同一个密码，无需每次重置后的改密操作。\n\n**获取方式**\n\n添加学谦微信powerxueqian询问价格，暗号：Pro管理员\n\n仅有20套，出完即止。"
  },
  {
    "path": "_posts/2023-07-02-什么是PowerBI全局管理员.md",
    "content": "# 什么是PowerBI全局管理员\n\n[了解 Power BI 管理员角色 - Power BI | Microsoft Learn](https://learn.microsoft.com/zh-cn/power-bi/admin/service-admin-role)\n\n租户（tenant）：\n\n简单来说，就是每个公司或者个人直接向微软申请了一块地方，用来存放一些可以访问的资源集合。\n\n每一个租户都有一个原始的唯一域名：xxxx.onmicrosoft.com\n\n每一个租户都会有一个原始的管理员，也就是全局管理员。\n\n全局管理员可以对整个租户，也就是整个域，进行全面系统的管理，包括但不限于：\n\n创建、删除、更改和查询账户，对每个用户的权限进行设置；\n\n创建、删除和修改安全组；\n\n管理powerbi门户、自定义powerbi品牌、管理容量设置、管理嵌入代码、管理工作区\n\n**一、用户管理**\n\n你可以在这个页面对用户进行创建、删除、更改和查询操作\n\n[https://admin.microsoft.com/Adminportal/Home?#/users](https://admin.microsoft.com/Adminportal/Home?#/users)\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled.png)\n\n也可以对用户进行分组管理，实现更方便的权限设定与角色管理。\n\n**二、发布公开web开关控制**\n\n如果发布到工作区中的报告想要发布公开web，那么可以按照以下的步骤操作：\n\n打开可编辑的工作区中的报表，然后选择“文件”>“嵌入报表”>“发布到 Web (公共)” 。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%201.png)\n\n如果 Power BI 管理员尚未允许你创建嵌入代码，则可能需要与他们联系，进行打开。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%202.png)\n\n而如果你是管理员，那么这个开关是由你自己来控制的，而且还可以设置部分人可以发布公开web，另一些人无法发布。\n\n更重要的是，如果发现某些曾经设置为公开web发布的报告，现在不再适合进行公开，那么可以直接将嵌入代码取消，而不必删除报告。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%203.png)\n\n**三、管理公司品牌**\n\n比如打开powerbi首页，左上角会显示您的特有徽标：\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%204.png)\n\n用户在登录账号时，也会有特定的显示界面（请忽略我拙劣的审美）\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%205.png)\n\n**四、powerbi数据集无限刷新**\n\n默认的pro账号每天只有8次刷新，无论是定时刷新还是powerautomate自动刷新只有8次；\n\n而有了管理员权限之后，所有的试用账号都可以实现每天定时刷新48次+powerautomate无限次刷新，想要10分钟刷新就10分钟刷新，甚至10秒刷新一次都是可以的！\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled.jpeg)\n\n曾经我也发过几篇文章阐述api对powerbi数据集的刷新过程。\n\n**五、对租户进行管理设置**\n\n在这里有各式各样的设置，包括谁可以创建最新的fabric项目，谁可以试用fabric，谁可以试用R语言和python视觉对象，仪表板设置、数据流设置、数据导出和共享设置、安全性设置等等。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%206.png)\n\n**六、添加自己的域名**\n\n原始域名生成的账号格式：zhangsan@xxxxx.onmicrosoft.com，既长又不好记。\n\n而如果自己有域名，可以将域名与该租户进行绑定，比如有域名powerbi.bi，就可以创建xueqian@powerbi.bi这样的账号。\n\n![Untitled](https://picgo-1301351990.cos.ap-beijing.myqcloud.com/markdown/Untitled%207.png)\n\n七**、所有的api操作**\n\n全局管理员可以通过配置api对全域的几乎所有的操作，实现api控制。通过postman、python等语言或者工具实现对以上所有的工作的全面掌控。\n\n具体参考：\n\n[https://learn.microsoft.com/en-us/rest/api/power-bi/](https://learn.microsoft.com/en-us/rest/api/power-bi/)"
  },
  {
    "path": "about.html",
    "content": "---\nlayout: page\ntitle: \"About\"\ndescription: \"学谦\"\nheader-img: \"img/post-bg-rwd.jpg\"\n---\n\n<!-- Language Selector -->\n<!-- <select class=\"sel-lang\" onchange= \"onLanChange(this.options[this.options.selectedIndex].value)\">\n    <option value=\"0\" selected> 中文 Chinese </option>\n    <option value=\"1\"> 英文 English </option>\n</select> -->\n\n<!-- Chinese Version -->\n<div class=\"zh post-container\">\n\n    <!--copied from markdown -->\n    <blockquote><p>有志者、事竟成，破釜沉舟，百二秦关终属楚<br>\n    苦心人、天不负，卧薪尝胆，三千越甲可吞吴</p></blockquote>\n\n    <p>陈泽满，字<strong>学谦</strong>。中国科学院大学海洋地质学硕士。</p>\n\n    <p>教育类上市公司商业智能架构工程师、PowerBI培训师</p>\n    \n    <p>熟悉C、python、SQL、DAX、M、Fortran、MATLAB等语言，擅长PowerBI商业智能分析与python数据分析</p>\n\n\n    <p>这是我在GitHub上搭建的个人博客。我在GitHub上的主页<a href=\"https://github.com/xueqiandata\">👉GitHub·学谦</a> \n        以及个人公众号：<a href=\"https://mp.weixin.qq.com/s/OrDqdlriIMkNZ_GzYUS92w\">👉公众号·学谦</a> 。如果有什么问题，欢迎提出探讨~</p>\n\n    <p></p>\n    \n    <h5>欢迎交流</h5>\n\n</div>\n\n<!-- English Version -->\n<!-- <div class=\"en post-container\">\n    <blockquote><p>Yet another iOS Developer. <br>\n    Yet another Life-long Student.</p></blockquote>\n\n    <p>Hi, I am <strong>xueqian</strong>，you can call me <strong>XQ</strong>. I am an iOS software engineer and currently working in Xiamen</p>\n\n    <p>This is my personal blog, through making Github Pages and Jekyll.My GitHub  👉 <a href=\"http://github.com/xueqiandata\">Github·xueqian</a>.</p>\n    \n    <p>I am a sports enthusiast, I like fitness, running and boxing.</p>\n\n    <h5>Talks</h5>\n\n</div> -->\n\n<!-- Handle Language Change -->\n<script type=\"text/javascript\">\n    // get nodes\n    var $zh = document.querySelector(\".zh\");\n    var $en = document.querySelector(\".en\");\n    var $select = document.querySelector(\"select\");\n\n    // bind hashchange event\n    window.addEventListener('hashchange', _render);\n\n    // handle render\n    function _render(){\n        var _hash = window.location.hash;\n        // en\n        if(_hash == \"#en\"){\n            $select.selectedIndex = 1;\n            $en.style.display = \"block\";\n            $zh.style.display = \"none\";\n        // zh by default\n        }else{\n            // not trigger onChange, otherwise cause a loop call.\n            $select.selectedIndex = 0;\n            $zh.style.display = \"block\";\n            $en.style.display = \"none\";\n        }\n    }\n\n    // handle select change\n    function onLanChange(index){\n        if(index == 0){\n            window.location.hash = \"#zh\"\n        }else{\n            window.location.hash = \"#en\"\n        }\n    }\n\n    // init\n    _render();\n</script>\n\n\n<!-- Gitalk 评论 start  -->\n{% if site.gitalk.enable %}\n<!-- Gitalk link  -->\n<link rel=\"stylesheet\" href=\"https://unpkg.com/gitalk/dist/gitalk.css\">\n<script src=\"https://unpkg.com/gitalk@latest/dist/gitalk.min.js\"></script>\n\n<div id=\"gitalk-container\"></div>\n    <script type=\"text/javascript\">\n    var gitalk = new Gitalk({\n    clientID: '14a053a5e3f367d2e515',\n    clientSecret: '350401a27d8a01c2c5a657242449692fd592d42a',\n    repo: 'xueqiandata.github.io',\n    owner: 'xueqiandata',\n    admin: ['xueqiandata'],\n    distractionFreeMode: {{site.gitalk.distractionFreeMode}},\n    id: 'about',\n    });\n    gitalk.render('gitalk-container');\n</script>\n{% endif %}\n<!-- Gitalk end -->\n\n <!-- disqus 评论框 start  -->\n{% if site.disqus.enable %}\n\n<div class=\"comment\">\n    <div id=\"disqus_thread\" class=\"disqus-thread\">\n    </div>\n</div>\n<!-- disqus 评论框 end -->\n\n<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->\n<script type=\"text/javascript\">\n    /* * * CONFIGURATION VARIABLES * * */\n    var disqus_shortname = \"{{site.disqus.username}}\";\n    var disqus_identifier = \"{{site.disqus.username}}/{{page.url}}\";\n    var disqus_url = \"{{site.url}}{{page.url}}\";\n\n    (function() {\n        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';\n        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n    })();\n</script>\n<!-- disqus 公共JS代码 end -->\n{% endif %}\n\n"
  },
  {
    "path": "codecov.yml",
    "content": "codecov:\n  token: d8b2c89f-64a9-4b9a-ac44-da4e871caeff\n"
  },
  {
    "path": "css/bootstrap.css",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n  font-family: sans-serif;\n  -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block;\n  vertical-align: baseline;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden],\ntemplate {\n  display: none;\n}\na {\n  background-color: transparent;\n}\na:active,\na:hover {\n  outline: 0;\n}\nabbr[title] {\n  border-bottom: 1px dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nh1 {\n  margin: .67em 0;\n  font-size: 2em;\n}\nmark {\n  color: #000;\n  background: #ff0;\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  position: relative;\n  font-size: 75%;\n  line-height: 0;\n  vertical-align: baseline;\n}\nsup {\n  top: -.5em;\n}\nsub {\n  bottom: -.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 1em 40px;\n}\nhr {\n  height: 0;\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n}\npre {\n  overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  margin: 0;\n  font: inherit;\n  color: inherit;\n}\nbutton {\n  overflow: visible;\n}\nbutton,\nselect {\n  text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  padding: 0;\n  border: 0;\n}\ninput {\n  line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n  -webkit-appearance: textfield;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nfieldset {\n  padding: .35em .625em .75em;\n  margin: 0 2px;\n  border: 1px solid #c0c0c0;\n}\nlegend {\n  padding: 0;\n  border: 0;\n}\ntextarea {\n  overflow: auto;\n}\noptgroup {\n  font-weight: bold;\n}\ntable {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\ntd,\nth {\n  padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n  *,\n  *:before,\n  *:after {\n    color: #000 !important;\n    text-shadow: none !important;\n    background: transparent !important;\n    -webkit-box-shadow: none !important;\n            box-shadow: none !important;\n  }\n  a,\n  a:visited {\n    text-decoration: underline;\n  }\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n  abbr[title]:after {\n    content: \" (\" attr(title) \")\";\n  }\n  a[href^=\"#\"]:after,\n  a[href^=\"javascript:\"]:after {\n    content: \"\";\n  }\n  pre,\n  blockquote {\n    border: 1px solid #999;\n\n    page-break-inside: avoid;\n  }\n  thead {\n    display: table-header-group;\n  }\n  tr,\n  img {\n    page-break-inside: avoid;\n  }\n  img {\n    max-width: 100% !important;\n  }\n  p,\n  h2,\n  h3 {\n    orphans: 3;\n    widows: 3;\n  }\n  h2,\n  h3 {\n    page-break-after: avoid;\n  }\n  select {\n    background: #fff !important;\n  }\n  .navbar {\n    display: none;\n  }\n  .btn > .caret,\n  .dropup > .btn > .caret {\n    border-top-color: #000 !important;\n  }\n  .label {\n    border: 1px solid #000;\n  }\n  .table {\n    border-collapse: collapse !important;\n  }\n  .table td,\n  .table th {\n    background-color: #fff !important;\n  }\n  .table-bordered th,\n  .table-bordered td {\n    border: 1px solid #ddd !important;\n  }\n}\n@font-face {\n  font-family: 'Glyphicons Halflings';\n\n  src: url('../fonts/glyphicons-halflings-regular.eot');\n  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: 'Glyphicons Halflings';\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n  content: \"\\2a\";\n}\n.glyphicon-plus:before {\n  content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n  content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n  content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n  content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n  content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n  content: \"\\270f\";\n}\n.glyphicon-glass:before {\n  content: \"\\e001\";\n}\n.glyphicon-music:before {\n  content: \"\\e002\";\n}\n.glyphicon-search:before {\n  content: \"\\e003\";\n}\n.glyphicon-heart:before {\n  content: \"\\e005\";\n}\n.glyphicon-star:before {\n  content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n  content: \"\\e007\";\n}\n.glyphicon-user:before {\n  content: \"\\e008\";\n}\n.glyphicon-film:before {\n  content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n  content: \"\\e010\";\n}\n.glyphicon-th:before {\n  content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n  content: \"\\e012\";\n}\n.glyphicon-ok:before {\n  content: \"\\e013\";\n}\n.glyphicon-remove:before {\n  content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n  content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n  content: \"\\e016\";\n}\n.glyphicon-off:before {\n  content: \"\\e017\";\n}\n.glyphicon-signal:before {\n  content: \"\\e018\";\n}\n.glyphicon-cog:before {\n  content: \"\\e019\";\n}\n.glyphicon-trash:before {\n  content: \"\\e020\";\n}\n.glyphicon-home:before {\n  content: \"\\e021\";\n}\n.glyphicon-file:before {\n  content: \"\\e022\";\n}\n.glyphicon-time:before {\n  content: \"\\e023\";\n}\n.glyphicon-road:before {\n  content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n  content: \"\\e025\";\n}\n.glyphicon-download:before {\n  content: \"\\e026\";\n}\n.glyphicon-upload:before {\n  content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n  content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n  content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n  content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n  content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n  content: \"\\e032\";\n}\n.glyphicon-lock:before {\n  content: \"\\e033\";\n}\n.glyphicon-flag:before {\n  content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n  content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n  content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n  content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n  content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n  content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n  content: \"\\e040\";\n}\n.glyphicon-tag:before {\n  content: \"\\e041\";\n}\n.glyphicon-tags:before {\n  content: \"\\e042\";\n}\n.glyphicon-book:before {\n  content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n  content: \"\\e044\";\n}\n.glyphicon-print:before {\n  content: \"\\e045\";\n}\n.glyphicon-camera:before {\n  content: \"\\e046\";\n}\n.glyphicon-font:before {\n  content: \"\\e047\";\n}\n.glyphicon-bold:before {\n  content: \"\\e048\";\n}\n.glyphicon-italic:before {\n  content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n  content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n  content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n  content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n  content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n  content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n  content: \"\\e055\";\n}\n.glyphicon-list:before {\n  content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n  content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n  content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n  content: \"\\e059\";\n}\n.glyphicon-picture:before {\n  content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n  content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n  content: \"\\e063\";\n}\n.glyphicon-tint:before {\n  content: \"\\e064\";\n}\n.glyphicon-edit:before {\n  content: \"\\e065\";\n}\n.glyphicon-share:before {\n  content: \"\\e066\";\n}\n.glyphicon-check:before {\n  content: \"\\e067\";\n}\n.glyphicon-move:before {\n  content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n  content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n  content: \"\\e070\";\n}\n.glyphicon-backward:before {\n  content: \"\\e071\";\n}\n.glyphicon-play:before {\n  content: \"\\e072\";\n}\n.glyphicon-pause:before {\n  content: \"\\e073\";\n}\n.glyphicon-stop:before {\n  content: \"\\e074\";\n}\n.glyphicon-forward:before {\n  content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n  content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n  content: \"\\e077\";\n}\n.glyphicon-eject:before {\n  content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n  content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n  content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n  content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n  content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n  content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n  content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n  content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n  content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n  content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n  content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n  content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n  content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n  content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n  content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n  content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n  content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n  content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n  content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n  content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n  content: \"\\e101\";\n}\n.glyphicon-gift:before {\n  content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n  content: \"\\e103\";\n}\n.glyphicon-fire:before {\n  content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n  content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n  content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n  content: \"\\e107\";\n}\n.glyphicon-plane:before {\n  content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n  content: \"\\e109\";\n}\n.glyphicon-random:before {\n  content: \"\\e110\";\n}\n.glyphicon-comment:before {\n  content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n  content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n  content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n  content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n  content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n  content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n  content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n  content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n  content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n  content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n  content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n  content: \"\\e122\";\n}\n.glyphicon-bell:before {\n  content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n  content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n  content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n  content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n  content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n  content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n  content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n  content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n  content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n  content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n  content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n  content: \"\\e134\";\n}\n.glyphicon-globe:before {\n  content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n  content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n  content: \"\\e137\";\n}\n.glyphicon-filter:before {\n  content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n  content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n  content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n  content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n  content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n  content: \"\\e143\";\n}\n.glyphicon-link:before {\n  content: \"\\e144\";\n}\n.glyphicon-phone:before {\n  content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n  content: \"\\e146\";\n}\n.glyphicon-usd:before {\n  content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n  content: \"\\e149\";\n}\n.glyphicon-sort:before {\n  content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n  content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n  content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n  content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n  content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n  content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n  content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n  content: \"\\e157\";\n}\n.glyphicon-expand:before {\n  content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n  content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n  content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n  content: \"\\e161\";\n}\n.glyphicon-flash:before {\n  content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n  content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n  content: \"\\e164\";\n}\n.glyphicon-record:before {\n  content: \"\\e165\";\n}\n.glyphicon-save:before {\n  content: \"\\e166\";\n}\n.glyphicon-open:before {\n  content: \"\\e167\";\n}\n.glyphicon-saved:before {\n  content: \"\\e168\";\n}\n.glyphicon-import:before {\n  content: \"\\e169\";\n}\n.glyphicon-export:before {\n  content: \"\\e170\";\n}\n.glyphicon-send:before {\n  content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n  content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n  content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n  content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n  content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n  content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n  content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n  content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n  content: \"\\e179\";\n}\n.glyphicon-header:before {\n  content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n  content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n  content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n  content: \"\\e183\";\n}\n.glyphicon-tower:before {\n  content: \"\\e184\";\n}\n.glyphicon-stats:before {\n  content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n  content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n  content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n  content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n  content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n  content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n  content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n  content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n  content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n  content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n  content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n  content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n  content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n  content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n  content: \"\\e200\";\n}\n.glyphicon-cd:before {\n  content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n  content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n  content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n  content: \"\\e204\";\n}\n.glyphicon-copy:before {\n  content: \"\\e205\";\n}\n.glyphicon-paste:before {\n  content: \"\\e206\";\n}\n.glyphicon-alert:before {\n  content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n  content: \"\\e210\";\n}\n.glyphicon-king:before {\n  content: \"\\e211\";\n}\n.glyphicon-queen:before {\n  content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n  content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n  content: \"\\e214\";\n}\n.glyphicon-knight:before {\n  content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n  content: \"\\e216\";\n}\n.glyphicon-tent:before {\n  content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n  content: \"\\e218\";\n}\n.glyphicon-bed:before {\n  content: \"\\e219\";\n}\n.glyphicon-apple:before {\n  content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n  content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n  content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n  content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n  content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n  content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n  content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n  content: \"\\e227\";\n}\n.glyphicon-yen:before {\n  content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n  content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n  content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n  content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n  content: \"\\e232\";\n}\n.glyphicon-education:before {\n  content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n  content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n  content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n  content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n  content: \"\\e237\";\n}\n.glyphicon-oil:before {\n  content: \"\\e238\";\n}\n.glyphicon-grain:before {\n  content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n  content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n  content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n  content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n  content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n  content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n  content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n  content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n  content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n  content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n  content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n  content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n  content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n  content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n  content: \"\\e253\";\n}\n.glyphicon-console:before {\n  content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n  content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n  content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n  content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n  content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n  content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n  content: \"\\e260\";\n}\n* {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\n*:before,\n*:after {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\nhtml {\n  font-size: 10px;\n\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #333;\n  background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\na {\n  color: #337ab7;\n  text-decoration: none;\n}\na:hover,\na:focus {\n  color: #23527c;\n  text-decoration: underline;\n}\na:focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\nfigure {\n  margin: 0;\n}\nimg {\n  vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n}\n.img-rounded {\n  border-radius: 6px;\n}\n.img-thumbnail {\n  display: inline-block;\n  max-width: 100%;\n  height: auto;\n  padding: 4px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: all .2s ease-in-out;\n       -o-transition: all .2s ease-in-out;\n          transition: all .2s ease-in-out;\n}\n.img-circle {\n  border-radius: 50%;\n}\nhr {\n  margin-top: 20px;\n  margin-bottom: 20px;\n  border: 0;\n  border-top: 1px solid #eee;\n}\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n  position: static;\n  width: auto;\n  height: auto;\n  margin: 0;\n  overflow: visible;\n  clip: auto;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  font-family: inherit;\n  font-weight: 500;\n  line-height: 1.1;\n  color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n  font-weight: normal;\n  line-height: 1;\n  color: #777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n  font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n  font-size: 75%;\n}\nh1,\n.h1 {\n  font-size: 36px;\n}\nh2,\n.h2 {\n  font-size: 30px;\n}\nh3,\n.h3 {\n  font-size: 24px;\n}\nh4,\n.h4 {\n  font-size: 18px;\n}\nh5,\n.h5 {\n  font-size: 14px;\n}\nh6,\n.h6 {\n  font-size: 12px;\n}\np {\n  margin: 0 0 10px;\n}\n.lead {\n  margin-bottom: 20px;\n  font-size: 16px;\n  font-weight: 300;\n  line-height: 1.4;\n}\n@media (min-width: 768px) {\n  .lead {\n    font-size: 21px;\n  }\n}\nsmall,\n.small {\n  font-size: 85%;\n}\nmark,\n.mark {\n  padding: .2em;\n  background-color: #fcf8e3;\n}\n.text-left {\n  text-align: left;\n}\n.text-right {\n  text-align: right;\n}\n.text-center {\n  text-align: center;\n}\n.text-justify {\n  text-align: justify;\n}\n.text-nowrap {\n  white-space: nowrap;\n}\n.text-lowercase {\n  text-transform: lowercase;\n}\n.text-uppercase {\n  text-transform: uppercase;\n}\n.text-capitalize {\n  text-transform: capitalize;\n}\n.text-muted {\n  color: #777;\n}\n.text-primary {\n  color: #337ab7;\n}\na.text-primary:hover {\n  color: #286090;\n}\n.text-success {\n  color: #3c763d;\n}\na.text-success:hover {\n  color: #2b542c;\n}\n.text-info {\n  color: #31708f;\n}\na.text-info:hover {\n  color: #245269;\n}\n.text-warning {\n  color: #8a6d3b;\n}\na.text-warning:hover {\n  color: #66512c;\n}\n.text-danger {\n  color: #a94442;\n}\na.text-danger:hover {\n  color: #843534;\n}\n.bg-primary {\n  color: #fff;\n  background-color: #337ab7;\n}\na.bg-primary:hover {\n  background-color: #286090;\n}\n.bg-success {\n  background-color: #dff0d8;\n}\na.bg-success:hover {\n  background-color: #c1e2b3;\n}\n.bg-info {\n  background-color: #d9edf7;\n}\na.bg-info:hover {\n  background-color: #afd9ee;\n}\n.bg-warning {\n  background-color: #fcf8e3;\n}\na.bg-warning:hover {\n  background-color: #f7ecb5;\n}\n.bg-danger {\n  background-color: #f2dede;\n}\na.bg-danger:hover {\n  background-color: #e4b9b9;\n}\n.page-header {\n  padding-bottom: 9px;\n  margin: 40px 0 20px;\n  border-bottom: 1px solid #eee;\n}\nul,\nol {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n  margin-bottom: 0;\n}\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n.list-inline {\n  padding-left: 0;\n  margin-left: -5px;\n  list-style: none;\n}\n.list-inline > li {\n  display: inline-block;\n  padding-right: 5px;\n  padding-left: 5px;\n}\ndl {\n  margin-top: 0;\n  margin-bottom: 20px;\n}\ndt,\ndd {\n  line-height: 1.42857143;\n}\ndt {\n  font-weight: bold;\n}\ndd {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .dl-horizontal dt {\n    float: left;\n    width: 160px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .dl-horizontal dd {\n    margin-left: 180px;\n  }\n}\nabbr[title],\nabbr[data-original-title] {\n  cursor: help;\n  border-bottom: 1px dotted #777;\n}\n.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\nblockquote {\n  padding: 10px 20px;\n  margin: 0 0 20px;\n  font-size: 17.5px;\n  border-left: 5px solid #eee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n  margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n  display: block;\n  font-size: 80%;\n  line-height: 1.42857143;\n  color: #777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n  content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n  padding-right: 15px;\n  padding-left: 0;\n  text-align: right;\n  border-right: 5px solid #eee;\n  border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n  content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n  content: '\\00A0 \\2014';\n}\naddress {\n  margin-bottom: 20px;\n  font-style: normal;\n  line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #c7254e;\n  background-color: #f9f2f4;\n  border-radius: 4px;\n}\nkbd {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #fff;\n  background-color: #333;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n}\nkbd kbd {\n  padding: 0;\n  font-size: 100%;\n  font-weight: bold;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\npre {\n  display: block;\n  padding: 9.5px;\n  margin: 0 0 10px;\n  font-size: 13px;\n  line-height: 1.42857143;\n  color: #333;\n  word-break: break-all;\n  word-wrap: break-word;\n  background-color: #f5f5f5;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\npre code {\n  padding: 0;\n  font-size: inherit;\n  color: inherit;\n  white-space: pre-wrap;\n  background-color: transparent;\n  border-radius: 0;\n}\n.pre-scrollable {\n  max-height: 340px;\n  overflow-y: scroll;\n}\n.container {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n@media (min-width: 768px) {\n  .container {\n    width: 750px;\n  }\n}\n@media (min-width: 992px) {\n  .container {\n    width: 970px;\n  }\n}\n@media (min-width: 1200px) {\n  .container {\n    width: 1170px;\n  }\n}\n.container-fluid {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n.row {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n  position: relative;\n  min-height: 1px;\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n  float: left;\n}\n.col-xs-12 {\n  width: 100%;\n}\n.col-xs-11 {\n  width: 91.66666667%;\n}\n.col-xs-10 {\n  width: 83.33333333%;\n}\n.col-xs-9 {\n  width: 75%;\n}\n.col-xs-8 {\n  width: 66.66666667%;\n}\n.col-xs-7 {\n  width: 58.33333333%;\n}\n.col-xs-6 {\n  width: 50%;\n}\n.col-xs-5 {\n  width: 41.66666667%;\n}\n.col-xs-4 {\n  width: 33.33333333%;\n}\n.col-xs-3 {\n  width: 25%;\n}\n.col-xs-2 {\n  width: 16.66666667%;\n}\n.col-xs-1 {\n  width: 8.33333333%;\n}\n.col-xs-pull-12 {\n  right: 100%;\n}\n.col-xs-pull-11 {\n  right: 91.66666667%;\n}\n.col-xs-pull-10 {\n  right: 83.33333333%;\n}\n.col-xs-pull-9 {\n  right: 75%;\n}\n.col-xs-pull-8 {\n  right: 66.66666667%;\n}\n.col-xs-pull-7 {\n  right: 58.33333333%;\n}\n.col-xs-pull-6 {\n  right: 50%;\n}\n.col-xs-pull-5 {\n  right: 41.66666667%;\n}\n.col-xs-pull-4 {\n  right: 33.33333333%;\n}\n.col-xs-pull-3 {\n  right: 25%;\n}\n.col-xs-pull-2 {\n  right: 16.66666667%;\n}\n.col-xs-pull-1 {\n  right: 8.33333333%;\n}\n.col-xs-pull-0 {\n  right: auto;\n}\n.col-xs-push-12 {\n  left: 100%;\n}\n.col-xs-push-11 {\n  left: 91.66666667%;\n}\n.col-xs-push-10 {\n  left: 83.33333333%;\n}\n.col-xs-push-9 {\n  left: 75%;\n}\n.col-xs-push-8 {\n  left: 66.66666667%;\n}\n.col-xs-push-7 {\n  left: 58.33333333%;\n}\n.col-xs-push-6 {\n  left: 50%;\n}\n.col-xs-push-5 {\n  left: 41.66666667%;\n}\n.col-xs-push-4 {\n  left: 33.33333333%;\n}\n.col-xs-push-3 {\n  left: 25%;\n}\n.col-xs-push-2 {\n  left: 16.66666667%;\n}\n.col-xs-push-1 {\n  left: 8.33333333%;\n}\n.col-xs-push-0 {\n  left: auto;\n}\n.col-xs-offset-12 {\n  margin-left: 100%;\n}\n.col-xs-offset-11 {\n  margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n  margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n  margin-left: 75%;\n}\n.col-xs-offset-8 {\n  margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n  margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n  margin-left: 50%;\n}\n.col-xs-offset-5 {\n  margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n  margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n  margin-left: 25%;\n}\n.col-xs-offset-2 {\n  margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n  margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n    float: left;\n  }\n  .col-sm-12 {\n    width: 100%;\n  }\n  .col-sm-11 {\n    width: 91.66666667%;\n  }\n  .col-sm-10 {\n    width: 83.33333333%;\n  }\n  .col-sm-9 {\n    width: 75%;\n  }\n  .col-sm-8 {\n    width: 66.66666667%;\n  }\n  .col-sm-7 {\n    width: 58.33333333%;\n  }\n  .col-sm-6 {\n    width: 50%;\n  }\n  .col-sm-5 {\n    width: 41.66666667%;\n  }\n  .col-sm-4 {\n    width: 33.33333333%;\n  }\n  .col-sm-3 {\n    width: 25%;\n  }\n  .col-sm-2 {\n    width: 16.66666667%;\n  }\n  .col-sm-1 {\n    width: 8.33333333%;\n  }\n  .col-sm-pull-12 {\n    right: 100%;\n  }\n  .col-sm-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-sm-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-sm-pull-9 {\n    right: 75%;\n  }\n  .col-sm-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-sm-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-sm-pull-6 {\n    right: 50%;\n  }\n  .col-sm-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-sm-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-sm-pull-3 {\n    right: 25%;\n  }\n  .col-sm-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-sm-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-sm-pull-0 {\n    right: auto;\n  }\n  .col-sm-push-12 {\n    left: 100%;\n  }\n  .col-sm-push-11 {\n    left: 91.66666667%;\n  }\n  .col-sm-push-10 {\n    left: 83.33333333%;\n  }\n  .col-sm-push-9 {\n    left: 75%;\n  }\n  .col-sm-push-8 {\n    left: 66.66666667%;\n  }\n  .col-sm-push-7 {\n    left: 58.33333333%;\n  }\n  .col-sm-push-6 {\n    left: 50%;\n  }\n  .col-sm-push-5 {\n    left: 41.66666667%;\n  }\n  .col-sm-push-4 {\n    left: 33.33333333%;\n  }\n  .col-sm-push-3 {\n    left: 25%;\n  }\n  .col-sm-push-2 {\n    left: 16.66666667%;\n  }\n  .col-sm-push-1 {\n    left: 8.33333333%;\n  }\n  .col-sm-push-0 {\n    left: auto;\n  }\n  .col-sm-offset-12 {\n    margin-left: 100%;\n  }\n  .col-sm-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-sm-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-sm-offset-9 {\n    margin-left: 75%;\n  }\n  .col-sm-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-sm-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-sm-offset-6 {\n    margin-left: 50%;\n  }\n  .col-sm-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-sm-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-sm-offset-3 {\n    margin-left: 25%;\n  }\n  .col-sm-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-sm-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-sm-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 992px) {\n  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n    float: left;\n  }\n  .col-md-12 {\n    width: 100%;\n  }\n  .col-md-11 {\n    width: 91.66666667%;\n  }\n  .col-md-10 {\n    width: 83.33333333%;\n  }\n  .col-md-9 {\n    width: 75%;\n  }\n  .col-md-8 {\n    width: 66.66666667%;\n  }\n  .col-md-7 {\n    width: 58.33333333%;\n  }\n  .col-md-6 {\n    width: 50%;\n  }\n  .col-md-5 {\n    width: 41.66666667%;\n  }\n  .col-md-4 {\n    width: 33.33333333%;\n  }\n  .col-md-3 {\n    width: 25%;\n  }\n  .col-md-2 {\n    width: 16.66666667%;\n  }\n  .col-md-1 {\n    width: 8.33333333%;\n  }\n  .col-md-pull-12 {\n    right: 100%;\n  }\n  .col-md-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-md-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-md-pull-9 {\n    right: 75%;\n  }\n  .col-md-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-md-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-md-pull-6 {\n    right: 50%;\n  }\n  .col-md-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-md-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-md-pull-3 {\n    right: 25%;\n  }\n  .col-md-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-md-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-md-pull-0 {\n    right: auto;\n  }\n  .col-md-push-12 {\n    left: 100%;\n  }\n  .col-md-push-11 {\n    left: 91.66666667%;\n  }\n  .col-md-push-10 {\n    left: 83.33333333%;\n  }\n  .col-md-push-9 {\n    left: 75%;\n  }\n  .col-md-push-8 {\n    left: 66.66666667%;\n  }\n  .col-md-push-7 {\n    left: 58.33333333%;\n  }\n  .col-md-push-6 {\n    left: 50%;\n  }\n  .col-md-push-5 {\n    left: 41.66666667%;\n  }\n  .col-md-push-4 {\n    left: 33.33333333%;\n  }\n  .col-md-push-3 {\n    left: 25%;\n  }\n  .col-md-push-2 {\n    left: 16.66666667%;\n  }\n  .col-md-push-1 {\n    left: 8.33333333%;\n  }\n  .col-md-push-0 {\n    left: auto;\n  }\n  .col-md-offset-12 {\n    margin-left: 100%;\n  }\n  .col-md-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-md-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-md-offset-9 {\n    margin-left: 75%;\n  }\n  .col-md-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-md-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-md-offset-6 {\n    margin-left: 50%;\n  }\n  .col-md-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-md-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-md-offset-3 {\n    margin-left: 25%;\n  }\n  .col-md-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-md-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-md-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 1200px) {\n  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n    float: left;\n  }\n  .col-lg-12 {\n    width: 100%;\n  }\n  .col-lg-11 {\n    width: 91.66666667%;\n  }\n  .col-lg-10 {\n    width: 83.33333333%;\n  }\n  .col-lg-9 {\n    width: 75%;\n  }\n  .col-lg-8 {\n    width: 66.66666667%;\n  }\n  .col-lg-7 {\n    width: 58.33333333%;\n  }\n  .col-lg-6 {\n    width: 50%;\n  }\n  .col-lg-5 {\n    width: 41.66666667%;\n  }\n  .col-lg-4 {\n    width: 33.33333333%;\n  }\n  .col-lg-3 {\n    width: 25%;\n  }\n  .col-lg-2 {\n    width: 16.66666667%;\n  }\n  .col-lg-1 {\n    width: 8.33333333%;\n  }\n  .col-lg-pull-12 {\n    right: 100%;\n  }\n  .col-lg-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-lg-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-lg-pull-9 {\n    right: 75%;\n  }\n  .col-lg-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-lg-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-lg-pull-6 {\n    right: 50%;\n  }\n  .col-lg-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-lg-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-lg-pull-3 {\n    right: 25%;\n  }\n  .col-lg-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-lg-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-lg-pull-0 {\n    right: auto;\n  }\n  .col-lg-push-12 {\n    left: 100%;\n  }\n  .col-lg-push-11 {\n    left: 91.66666667%;\n  }\n  .col-lg-push-10 {\n    left: 83.33333333%;\n  }\n  .col-lg-push-9 {\n    left: 75%;\n  }\n  .col-lg-push-8 {\n    left: 66.66666667%;\n  }\n  .col-lg-push-7 {\n    left: 58.33333333%;\n  }\n  .col-lg-push-6 {\n    left: 50%;\n  }\n  .col-lg-push-5 {\n    left: 41.66666667%;\n  }\n  .col-lg-push-4 {\n    left: 33.33333333%;\n  }\n  .col-lg-push-3 {\n    left: 25%;\n  }\n  .col-lg-push-2 {\n    left: 16.66666667%;\n  }\n  .col-lg-push-1 {\n    left: 8.33333333%;\n  }\n  .col-lg-push-0 {\n    left: auto;\n  }\n  .col-lg-offset-12 {\n    margin-left: 100%;\n  }\n  .col-lg-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-lg-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-lg-offset-9 {\n    margin-left: 75%;\n  }\n  .col-lg-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-lg-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-lg-offset-6 {\n    margin-left: 50%;\n  }\n  .col-lg-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-lg-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-lg-offset-3 {\n    margin-left: 25%;\n  }\n  .col-lg-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-lg-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-lg-offset-0 {\n    margin-left: 0;\n  }\n}\ntable {\n  background-color: transparent;\n}\ncaption {\n  padding-top: 8px;\n  padding-bottom: 8px;\n  color: #777;\n  text-align: left;\n}\nth {\n  text-align: left;\n}\n.table {\n  width: 100%;\n  max-width: 100%;\n  margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n  padding: 8px;\n  line-height: 1.42857143;\n  vertical-align: top;\n  border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n  vertical-align: bottom;\n  border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n  border-top: 0;\n}\n.table > tbody + tbody {\n  border-top: 2px solid #ddd;\n}\n.table .table {\n  background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n  padding: 5px;\n}\n.table-bordered {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n  border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n  background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n  background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n  position: static;\n  display: table-column;\n  float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n  position: static;\n  display: table-cell;\n  float: none;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n  background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n  background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n  background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n  background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n  background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n  background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n  background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n  background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n  background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n  background-color: #ebcccc;\n}\n.table-responsive {\n  min-height: .01%;\n  overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: 15px;\n    overflow-y: hidden;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid #ddd;\n  }\n  .table-responsive > .table {\n    margin-bottom: 0;\n  }\n  .table-responsive > .table > thead > tr > th,\n  .table-responsive > .table > tbody > tr > th,\n  .table-responsive > .table > tfoot > tr > th,\n  .table-responsive > .table > thead > tr > td,\n  .table-responsive > .table > tbody > tr > td,\n  .table-responsive > .table > tfoot > tr > td {\n    white-space: nowrap;\n  }\n  .table-responsive > .table-bordered {\n    border: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:first-child,\n  .table-responsive > .table-bordered > tbody > tr > th:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n  .table-responsive > .table-bordered > thead > tr > td:first-child,\n  .table-responsive > .table-bordered > tbody > tr > td:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n    border-left: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:last-child,\n  .table-responsive > .table-bordered > tbody > tr > th:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n  .table-responsive > .table-bordered > thead > tr > td:last-child,\n  .table-responsive > .table-bordered > tbody > tr > td:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n    border-right: 0;\n  }\n  .table-responsive > .table-bordered > tbody > tr:last-child > th,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n  .table-responsive > .table-bordered > tbody > tr:last-child > td,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n    border-bottom: 0;\n  }\n}\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 20px;\n  font-size: 21px;\n  line-height: inherit;\n  color: #333;\n  border: 0;\n  border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n  display: inline-block;\n  max-width: 100%;\n  margin-bottom: 5px;\n  font-weight: bold;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9;\n  line-height: normal;\n}\ninput[type=\"file\"] {\n  display: block;\n}\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\nselect[multiple],\nselect[size] {\n  height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\noutput {\n  display: block;\n  padding-top: 7px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n}\n.form-control {\n  display: block;\n  width: 100%;\n  height: 34px;\n  padding: 6px 12px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n  background-color: #fff;\n  background-image: none;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n       -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n          transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n  border-color: #66afe9;\n  outline: 0;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n          box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n}\n.form-control::-moz-placeholder {\n  color: #999;\n  opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n  color: #999;\n}\n.form-control::-webkit-input-placeholder {\n  color: #999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n  cursor: not-allowed;\n  background-color: #eee;\n  opacity: 1;\n}\ntextarea.form-control {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n  input[type=\"date\"],\n  input[type=\"time\"],\n  input[type=\"datetime-local\"],\n  input[type=\"month\"] {\n    line-height: 34px;\n  }\n  input[type=\"date\"].input-sm,\n  input[type=\"time\"].input-sm,\n  input[type=\"datetime-local\"].input-sm,\n  input[type=\"month\"].input-sm,\n  .input-group-sm input[type=\"date\"],\n  .input-group-sm input[type=\"time\"],\n  .input-group-sm input[type=\"datetime-local\"],\n  .input-group-sm input[type=\"month\"] {\n    line-height: 30px;\n  }\n  input[type=\"date\"].input-lg,\n  input[type=\"time\"].input-lg,\n  input[type=\"datetime-local\"].input-lg,\n  input[type=\"month\"].input-lg,\n  .input-group-lg input[type=\"date\"],\n  .input-group-lg input[type=\"time\"],\n  .input-group-lg input[type=\"datetime-local\"],\n  .input-group-lg input[type=\"month\"] {\n    line-height: 46px;\n  }\n}\n.form-group {\n  margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n  min-height: 20px;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-top: 4px \\9;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  vertical-align: middle;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n  cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n  cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n  cursor: not-allowed;\n}\n.form-control-static {\n  padding-top: 7px;\n  padding-bottom: 7px;\n  margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-sm {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-sm {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n  height: auto;\n}\n.form-group-sm .form-control {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n  height: auto;\n}\n.form-group-sm .form-control-static {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.input-lg {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-lg {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n  height: auto;\n}\n.form-group-lg .form-control {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n  height: auto;\n}\n.form-group-lg .form-control-static {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.has-feedback {\n  position: relative;\n}\n.has-feedback .form-control {\n  padding-right: 42.5px;\n}\n.form-control-feedback {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  display: block;\n  width: 34px;\n  height: 34px;\n  line-height: 34px;\n  text-align: center;\n  pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n  width: 46px;\n  height: 46px;\n  line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n  width: 30px;\n  height: 30px;\n  line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n  color: #3c763d;\n}\n.has-success .form-control {\n  border-color: #3c763d;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-success .form-control:focus {\n  border-color: #2b542c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n  color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n  color: #8a6d3b;\n}\n.has-warning .form-control {\n  border-color: #8a6d3b;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-warning .form-control:focus {\n  border-color: #66512c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n  color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n  color: #a94442;\n}\n.has-error .form-control {\n  border-color: #a94442;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-error .form-control:focus {\n  border-color: #843534;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #a94442;\n}\n.has-error .form-control-feedback {\n  color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n  top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n  top: 0;\n}\n.help-block {\n  display: block;\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: #737373;\n}\n@media (min-width: 768px) {\n  .form-inline .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .form-inline .form-control-static {\n    display: inline-block;\n  }\n  .form-inline .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .form-inline .input-group .input-group-addon,\n  .form-inline .input-group .input-group-btn,\n  .form-inline .input-group .form-control {\n    width: auto;\n  }\n  .form-inline .input-group > .form-control {\n    width: 100%;\n  }\n  .form-inline .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio,\n  .form-inline .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio label,\n  .form-inline .checkbox label {\n    padding-left: 0;\n  }\n  .form-inline .radio input[type=\"radio\"],\n  .form-inline .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .form-inline .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n  padding-top: 7px;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n  min-height: 27px;\n}\n.form-horizontal .form-group {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .control-label {\n    padding-top: 7px;\n    margin-bottom: 0;\n    text-align: right;\n  }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n  right: 15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-lg .control-label {\n    padding-top: 14.333333px;\n  }\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-sm .control-label {\n    padding-top: 6px;\n  }\n}\n.btn {\n  display: inline-block;\n  padding: 6px 12px;\n  margin-bottom: 0;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  -ms-touch-action: manipulation;\n      touch-action: manipulation;\n  cursor: pointer;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n  color: #333;\n  text-decoration: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n  outline: 0;\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n  pointer-events: none;\n  cursor: not-allowed;\n  filter: alpha(opacity=65);\n  -webkit-box-shadow: none;\n          box-shadow: none;\n  opacity: .65;\n}\n.btn-default {\n  color: #333;\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default .badge {\n  color: #fff;\n  background-color: #333;\n}\n.btn-primary {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  color: #fff;\n  background-color: #286090;\n  border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.btn-success {\n  color: #fff;\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success .badge {\n  color: #5cb85c;\n  background-color: #fff;\n}\n.btn-info {\n  color: #fff;\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info .badge {\n  color: #5bc0de;\n  background-color: #fff;\n}\n.btn-warning {\n  color: #fff;\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning .badge {\n  color: #f0ad4e;\n  background-color: #fff;\n}\n.btn-danger {\n  color: #fff;\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger .badge {\n  color: #d9534f;\n  background-color: #fff;\n}\n.btn-link {\n  font-weight: normal;\n  color: #337ab7;\n  border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n  border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n  color: #23527c;\n  text-decoration: underline;\n  background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n  color: #777;\n  text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n  padding: 1px 5px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-block {\n  display: block;\n  width: 100%;\n}\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n  width: 100%;\n}\n.fade {\n  opacity: 0;\n  -webkit-transition: opacity .15s linear;\n       -o-transition: opacity .15s linear;\n          transition: opacity .15s linear;\n}\n.fade.in {\n  opacity: 1;\n}\n.collapse {\n  display: none;\n  visibility: hidden;\n}\n.collapse.in {\n  display: block;\n  visibility: visible;\n}\ntr.collapse.in {\n  display: table-row;\n}\ntbody.collapse.in {\n  display: table-row-group;\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  -webkit-transition-timing-function: ease;\n       -o-transition-timing-function: ease;\n          transition-timing-function: ease;\n  -webkit-transition-duration: .35s;\n       -o-transition-duration: .35s;\n          transition-duration: .35s;\n  -webkit-transition-property: height, visibility;\n       -o-transition-property: height, visibility;\n          transition-property: height, visibility;\n}\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top: 4px solid;\n  border-right: 4px solid transparent;\n  border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n  position: relative;\n}\n.dropdown-toggle:focus {\n  outline: 0;\n}\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: 1000;\n  display: none;\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0;\n  font-size: 14px;\n  text-align: left;\n  list-style: none;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .15);\n  border-radius: 4px;\n  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n          box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n}\n.dropdown-menu.pull-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu .divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n  display: block;\n  padding: 3px 20px;\n  clear: both;\n  font-weight: normal;\n  line-height: 1.42857143;\n  color: #333;\n  white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  color: #262626;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  color: #fff;\n  text-decoration: none;\n  background-color: #337ab7;\n  outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  color: #777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n  background-image: none;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n  display: block;\n}\n.open > a {\n  outline: 0;\n}\n.dropdown-menu-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu-left {\n  right: auto;\n  left: 0;\n}\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: 12px;\n  line-height: 1.42857143;\n  color: #777;\n  white-space: nowrap;\n}\n.dropdown-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 990;\n}\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n  content: \"\";\n  border-top: 0;\n  border-bottom: 4px solid;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n  top: auto;\n  bottom: 100%;\n  margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n  .navbar-right .dropdown-menu {\n    right: 0;\n    left: auto;\n  }\n  .navbar-right .dropdown-menu-left {\n    right: auto;\n    left: 0;\n  }\n}\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n  z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n  margin-left: -1px;\n}\n.btn-toolbar {\n  margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n  float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n  margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n.btn-group > .btn:first-child {\n  margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n  padding-right: 8px;\n  padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-right: 12px;\n  padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn .caret {\n  margin-left: 0;\n}\n.btn-lg .caret {\n  border-width: 5px 5px 0;\n  border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n  border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n  display: block;\n  float: none;\n  width: 100%;\n  max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n  float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n  margin-top: -1px;\n  margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n  border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n  display: table-cell;\n  float: none;\n  width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n  width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n  left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.input-group {\n  position: relative;\n  display: table;\n  border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n  float: none;\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-group .form-control {\n  position: relative;\n  z-index: 2;\n  float: left;\n  width: 100%;\n  margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle;\n}\n.input-group-addon {\n  padding: 6px 12px;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1;\n  color: #555;\n  text-align: center;\n  background-color: #eee;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n.input-group-addon.input-sm {\n  padding: 5px 10px;\n  font-size: 12px;\n  border-radius: 3px;\n}\n.input-group-addon.input-lg {\n  padding: 10px 16px;\n  font-size: 18px;\n  border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n  margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n.input-group-btn {\n  position: relative;\n  font-size: 0;\n  white-space: nowrap;\n}\n.input-group-btn > .btn {\n  position: relative;\n}\n.input-group-btn > .btn + .btn {\n  margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n  z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n  margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n  margin-left: -1px;\n}\n.nav {\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.nav > li {\n  position: relative;\n  display: block;\n}\n.nav > li > a {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.nav > li.disabled > a {\n  color: #777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n  color: #777;\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n  background-color: #eee;\n  border-color: #337ab7;\n}\n.nav .nav-divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.nav > li > a > img {\n  max-width: none;\n}\n.nav-tabs {\n  border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n  float: left;\n  margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n  margin-right: 2px;\n  line-height: 1.42857143;\n  border: 1px solid transparent;\n  border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n  border-color: #eee #eee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n  color: #555;\n  cursor: default;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n  width: 100%;\n  border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n  float: none;\n}\n.nav-tabs.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-tabs.nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs.nav-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs.nav-justified > .active > a,\n  .nav-tabs.nav-justified > .active > a:hover,\n  .nav-tabs.nav-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.nav-pills > li {\n  float: left;\n}\n.nav-pills > li > a {\n  border-radius: 4px;\n}\n.nav-pills > li + li {\n  margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n  color: #fff;\n  background-color: #337ab7;\n}\n.nav-stacked > li {\n  float: none;\n}\n.nav-stacked > li + li {\n  margin-top: 2px;\n  margin-left: 0;\n}\n.nav-justified {\n  width: 100%;\n}\n.nav-justified > li {\n  float: none;\n}\n.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs-justified {\n  border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs-justified > .active > a,\n  .nav-tabs-justified > .active > a:hover,\n  .nav-tabs-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.tab-content > .tab-pane {\n  display: none;\n  visibility: hidden;\n}\n.tab-content > .active {\n  display: block;\n  visibility: visible;\n}\n.nav-tabs .dropdown-menu {\n  margin-top: -1px;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar {\n  position: relative;\n  min-height: 50px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n  .navbar {\n    border-radius: 4px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-header {\n    float: left;\n  }\n}\n.navbar-collapse {\n  padding-right: 15px;\n  padding-left: 15px;\n  overflow-x: visible;\n  -webkit-overflow-scrolling: touch;\n  border-top: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n}\n.navbar-collapse.in {\n  overflow-y: auto;\n}\n@media (min-width: 768px) {\n  .navbar-collapse {\n    width: auto;\n    border-top: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-collapse.collapse {\n    display: block !important;\n    height: auto !important;\n    padding-bottom: 0;\n    overflow: visible !important;\n    visibility: visible !important;\n  }\n  .navbar-collapse.in {\n    overflow-y: visible;\n  }\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-static-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    padding-right: 0;\n    padding-left: 0;\n  }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n  max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    max-height: 200px;\n  }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .container > .navbar-header,\n  .container-fluid > .navbar-header,\n  .container > .navbar-collapse,\n  .container-fluid > .navbar-collapse {\n    margin-right: 0;\n    margin-left: 0;\n  }\n}\n.navbar-static-top {\n  z-index: 1000;\n  border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n  .navbar-static-top {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n@media (min-width: 768px) {\n  .navbar-fixed-top,\n  .navbar-fixed-bottom {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  top: 0;\n  border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0;\n  border-width: 1px 0 0;\n}\n.navbar-brand {\n  float: left;\n  height: 50px;\n  padding: 15px 15px;\n  font-size: 18px;\n  line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n  text-decoration: none;\n}\n.navbar-brand > img {\n  display: block;\n}\n@media (min-width: 768px) {\n  .navbar > .container .navbar-brand,\n  .navbar > .container-fluid .navbar-brand {\n    margin-left: -15px;\n  }\n}\n.navbar-toggle {\n  position: relative;\n  float: right;\n  padding: 9px 10px;\n  margin-top: 8px;\n  margin-right: 15px;\n  margin-bottom: 8px;\n  background-color: transparent;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.navbar-toggle:focus {\n  outline: 0;\n}\n.navbar-toggle .icon-bar {\n  display: block;\n  width: 22px;\n  height: 2px;\n  border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 4px;\n}\n@media (min-width: 768px) {\n  .navbar-toggle {\n    display: none;\n  }\n}\n.navbar-nav {\n  margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n  padding-top: 10px;\n  padding-bottom: 10px;\n  line-height: 20px;\n}\n@media (max-width: 767px) {\n  .navbar-nav .open .dropdown-menu {\n    position: static;\n    float: none;\n    width: auto;\n    margin-top: 0;\n    background-color: transparent;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-nav .open .dropdown-menu > li > a,\n  .navbar-nav .open .dropdown-menu .dropdown-header {\n    padding: 5px 15px 5px 25px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a {\n    line-height: 20px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-nav .open .dropdown-menu > li > a:focus {\n    background-image: none;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-nav {\n    float: left;\n    margin: 0;\n  }\n  .navbar-nav > li {\n    float: left;\n  }\n  .navbar-nav > li > a {\n    padding-top: 15px;\n    padding-bottom: 15px;\n  }\n}\n.navbar-form {\n  padding: 10px 15px;\n  margin-top: 8px;\n  margin-right: -15px;\n  margin-bottom: 8px;\n  margin-left: -15px;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n}\n@media (min-width: 768px) {\n  .navbar-form .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control-static {\n    display: inline-block;\n  }\n  .navbar-form .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .navbar-form .input-group .input-group-addon,\n  .navbar-form .input-group .input-group-btn,\n  .navbar-form .input-group .form-control {\n    width: auto;\n  }\n  .navbar-form .input-group > .form-control {\n    width: 100%;\n  }\n  .navbar-form .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio,\n  .navbar-form .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio label,\n  .navbar-form .checkbox label {\n    padding-left: 0;\n  }\n  .navbar-form .radio input[type=\"radio\"],\n  .navbar-form .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .navbar-form .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n@media (max-width: 767px) {\n  .navbar-form .form-group {\n    margin-bottom: 5px;\n  }\n  .navbar-form .form-group:last-child {\n    margin-bottom: 0;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-form {\n    width: auto;\n    padding-top: 0;\n    padding-bottom: 0;\n    margin-right: 0;\n    margin-left: 0;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n}\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  margin-bottom: 0;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.navbar-btn {\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n  margin-top: 14px;\n  margin-bottom: 14px;\n}\n.navbar-text {\n  margin-top: 15px;\n  margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n  .navbar-text {\n    float: left;\n    margin-right: 15px;\n    margin-left: 15px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-left {\n    float: left !important;\n  }\n  .navbar-right {\n    float: right !important;\n    margin-right: -15px;\n  }\n  .navbar-right ~ .navbar-right {\n    margin-right: 0;\n  }\n}\n.navbar-default {\n  background-color: #f8f8f8;\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n  color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n  color: #5e5e5e;\n  background-color: transparent;\n}\n.navbar-default .navbar-text {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n  color: #333;\n  background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n  color: #ccc;\n  background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n  border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n  background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n    color: #777;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #333;\n    background-color: transparent;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #555;\n    background-color: #e7e7e7;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #ccc;\n    background-color: transparent;\n  }\n}\n.navbar-default .navbar-link {\n  color: #777;\n}\n.navbar-default .navbar-link:hover {\n  color: #333;\n}\n.navbar-default .btn-link {\n  color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n  color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n  color: #ccc;\n}\n.navbar-inverse {\n  background-color: #222;\n  border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n  color: #444;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n  border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n  background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n  background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n  border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n@media (max-width: 767px) {\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n    border-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n    color: #9d9d9d;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #fff;\n    background-color: transparent;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #444;\n    background-color: transparent;\n  }\n}\n.navbar-inverse .navbar-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n  color: #fff;\n}\n.navbar-inverse .btn-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n  color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n  color: #444;\n}\n.breadcrumb {\n  padding: 8px 15px;\n  margin-bottom: 20px;\n  list-style: none;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n}\n.breadcrumb > li {\n  display: inline-block;\n}\n.breadcrumb > li + li:before {\n  padding: 0 5px;\n  color: #ccc;\n  content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n  color: #777;\n}\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: 20px 0;\n  border-radius: 4px;\n}\n.pagination > li {\n  display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n  position: relative;\n  float: left;\n  padding: 6px 12px;\n  margin-left: -1px;\n  line-height: 1.42857143;\n  color: #337ab7;\n  text-decoration: none;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n  margin-left: 0;\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n  color: #23527c;\n  background-color: #eee;\n  border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n  z-index: 2;\n  color: #fff;\n  cursor: default;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n  border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n  padding: 10px 16px;\n  font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n  border-top-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n  border-top-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n  padding: 5px 10px;\n  font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n.pager {\n  padding-left: 0;\n  margin: 20px 0;\n  text-align: center;\n  list-style: none;\n}\n.pager li {\n  display: inline;\n}\n.pager li > a,\n.pager li > span {\n  display: inline-block;\n  padding: 5px 14px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.pager .next > a,\n.pager .next > span {\n  float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n  float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n}\n.label {\n  display: inline;\n  padding: .2em .6em .3em;\n  font-size: 75%;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.label:empty {\n  display: none;\n}\n.btn .label {\n  position: relative;\n  top: -1px;\n}\n.label-default {\n  background-color: #777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n  background-color: #5e5e5e;\n}\n.label-primary {\n  background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n  background-color: #286090;\n}\n.label-success {\n  background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n  background-color: #449d44;\n}\n.label-info {\n  background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n  background-color: #31b0d5;\n}\n.label-warning {\n  background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n  background-color: #ec971f;\n}\n.label-danger {\n  background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n  background-color: #c9302c;\n}\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: 12px;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  background-color: #777;\n  border-radius: 10px;\n}\n.badge:empty {\n  display: none;\n}\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n.btn-xs .badge {\n  top: 0;\n  padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.list-group-item > .badge {\n  float: right;\n}\n.list-group-item > .badge + .badge {\n  margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n.jumbotron {\n  padding: 30px 15px;\n  margin-bottom: 30px;\n  color: inherit;\n  background-color: #eee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n  color: inherit;\n}\n.jumbotron p {\n  margin-bottom: 15px;\n  font-size: 21px;\n  font-weight: 200;\n}\n.jumbotron > hr {\n  border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n  border-radius: 6px;\n}\n.jumbotron .container {\n  max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n  .jumbotron {\n    padding: 48px 0;\n  }\n  .container .jumbotron,\n  .container-fluid .jumbotron {\n    padding-right: 60px;\n    padding-left: 60px;\n  }\n  .jumbotron h1,\n  .jumbotron .h1 {\n    font-size: 63px;\n  }\n}\n.thumbnail {\n  display: block;\n  padding: 4px;\n  margin-bottom: 20px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: border .2s ease-in-out;\n       -o-transition: border .2s ease-in-out;\n          transition: border .2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n  margin-right: auto;\n  margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n  border-color: #337ab7;\n}\n.thumbnail .caption {\n  padding: 9px;\n  color: #333;\n}\n.alert {\n  padding: 15px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.alert h4 {\n  margin-top: 0;\n  color: inherit;\n}\n.alert .alert-link {\n  font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n  margin-bottom: 0;\n}\n.alert > p + p {\n  margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n  padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n  position: relative;\n  top: -2px;\n  right: -21px;\n  color: inherit;\n}\n.alert-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.alert-success hr {\n  border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n  color: #2b542c;\n}\n.alert-info {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.alert-info hr {\n  border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n  color: #245269;\n}\n.alert-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.alert-warning hr {\n  border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n  color: #66512c;\n}\n.alert-danger {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.alert-danger hr {\n  border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n  color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@-o-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n.progress {\n  height: 20px;\n  margin-bottom: 20px;\n  overflow: hidden;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n          box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n}\n.progress-bar {\n  float: left;\n  width: 0;\n  height: 100%;\n  font-size: 12px;\n  line-height: 20px;\n  color: #fff;\n  text-align: center;\n  background-color: #337ab7;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n  -webkit-transition: width .6s ease;\n       -o-transition: width .6s ease;\n          transition: width .6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  -webkit-background-size: 40px 40px;\n          background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n  -webkit-animation: progress-bar-stripes 2s linear infinite;\n       -o-animation: progress-bar-stripes 2s linear infinite;\n          animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n  background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n  background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n  background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n  background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n.media-body {\n  width: 10000px;\n}\n.media-object {\n  display: block;\n}\n.media-right,\n.media > .pull-right {\n  padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n  padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n  display: table-cell;\n  vertical-align: top;\n}\n.media-middle {\n  vertical-align: middle;\n}\n.media-bottom {\n  vertical-align: bottom;\n}\n.media-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n.list-group {\n  padding-left: 0;\n  margin-bottom: 20px;\n}\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n  margin-bottom: -1px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n  margin-bottom: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\na.list-group-item {\n  color: #555;\n}\na.list-group-item .list-group-item-heading {\n  color: #333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n  color: #555;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #eee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n  color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n  color: #777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  z-index: 2;\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n  color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n  color: #c7ddef;\n}\n.list-group-item-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n}\na.list-group-item-success {\n  color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n  color: #3c763d;\n  background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n  color: #fff;\n  background-color: #3c763d;\n  border-color: #3c763d;\n}\n.list-group-item-info {\n  color: #31708f;\n  background-color: #d9edf7;\n}\na.list-group-item-info {\n  color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n  color: #31708f;\n  background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n  color: #fff;\n  background-color: #31708f;\n  border-color: #31708f;\n}\n.list-group-item-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n}\na.list-group-item-warning {\n  color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n  color: #8a6d3b;\n  background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n  color: #fff;\n  background-color: #8a6d3b;\n  border-color: #8a6d3b;\n}\n.list-group-item-danger {\n  color: #a94442;\n  background-color: #f2dede;\n}\na.list-group-item-danger {\n  color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n  color: #a94442;\n  background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n  color: #fff;\n  background-color: #a94442;\n  border-color: #a94442;\n}\n.list-group-item-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.list-group-item-text {\n  margin-bottom: 0;\n  line-height: 1.3;\n}\n.panel {\n  margin-bottom: 20px;\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n}\n.panel-body {\n  padding: 15px;\n}\n.panel-heading {\n  padding: 10px 15px;\n  border-bottom: 1px solid transparent;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n  color: inherit;\n}\n.panel-title {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-size: 16px;\n  color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n  color: inherit;\n}\n.panel-footer {\n  padding: 10px 15px;\n  background-color: #f5f5f5;\n  border-top: 1px solid #ddd;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n  margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n  border-width: 1px 0;\n  border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n  border-top: 0;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n  border-bottom: 0;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n  border-top-width: 0;\n}\n.list-group + .panel-footer {\n  border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n  margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n  border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n  border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n  border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n  border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n  border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n  border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n  border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n  border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n  border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n  border-bottom: 0;\n}\n.panel > .table-responsive {\n  margin-bottom: 0;\n  border: 0;\n}\n.panel-group {\n  margin-bottom: 20px;\n}\n.panel-group .panel {\n  margin-bottom: 0;\n  border-radius: 4px;\n}\n.panel-group .panel + .panel {\n  margin-top: 5px;\n}\n.panel-group .panel-heading {\n  border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n  border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n  border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n  border-bottom: 1px solid #ddd;\n}\n.panel-default {\n  border-color: #ddd;\n}\n.panel-default > .panel-heading {\n  color: #333;\n  background-color: #f5f5f5;\n  border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n  color: #f5f5f5;\n  background-color: #333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ddd;\n}\n.panel-primary {\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #337ab7;\n}\n.panel-success {\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n  color: #dff0d8;\n  background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #d6e9c6;\n}\n.panel-info {\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n  color: #d9edf7;\n  background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #bce8f1;\n}\n.panel-warning {\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n  color: #fcf8e3;\n  background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #faebcc;\n}\n.panel-danger {\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n  color: #f2dede;\n  background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n  position: relative;\n  display: block;\n  height: 0;\n  padding: 0;\n  overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n.embed-responsive.embed-responsive-16by9 {\n  padding-bottom: 56.25%;\n}\n.embed-responsive.embed-responsive-4by3 {\n  padding-bottom: 75%;\n}\n.well {\n  min-height: 20px;\n  padding: 19px;\n  margin-bottom: 20px;\n  background-color: #f5f5f5;\n  border: 1px solid #e3e3e3;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n}\n.well blockquote {\n  border-color: #ddd;\n  border-color: rgba(0, 0, 0, .15);\n}\n.well-lg {\n  padding: 24px;\n  border-radius: 6px;\n}\n.well-sm {\n  padding: 9px;\n  border-radius: 3px;\n}\n.close {\n  float: right;\n  font-size: 21px;\n  font-weight: bold;\n  line-height: 1;\n  color: #000;\n  text-shadow: 0 1px 0 #fff;\n  filter: alpha(opacity=20);\n  opacity: .2;\n}\n.close:hover,\n.close:focus {\n  color: #000;\n  text-decoration: none;\n  cursor: pointer;\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\nbutton.close {\n  -webkit-appearance: none;\n  padding: 0;\n  cursor: pointer;\n  background: transparent;\n  border: 0;\n}\n.modal-open {\n  overflow: hidden;\n}\n.modal {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1040;\n  display: none;\n  overflow: hidden;\n  -webkit-overflow-scrolling: touch;\n  outline: 0;\n}\n.modal.fade .modal-dialog {\n  -webkit-transition: -webkit-transform .3s ease-out;\n       -o-transition:      -o-transform .3s ease-out;\n          transition:         transform .3s ease-out;\n  -webkit-transform: translate(0, -25%);\n      -ms-transform: translate(0, -25%);\n       -o-transform: translate(0, -25%);\n          transform: translate(0, -25%);\n}\n.modal.in .modal-dialog {\n  -webkit-transform: translate(0, 0);\n      -ms-transform: translate(0, 0);\n       -o-transform: translate(0, 0);\n          transform: translate(0, 0);\n}\n.modal-open .modal {\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: 10px;\n}\n.modal-content {\n  position: relative;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #999;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  outline: 0;\n  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n          box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n}\n.modal-backdrop {\n  position: absolute;\n  top: 0;\n  right: 0;\n  left: 0;\n  background-color: #000;\n}\n.modal-backdrop.fade {\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.modal-backdrop.in {\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.modal-header {\n  min-height: 16.42857143px;\n  padding: 15px;\n  border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n  margin-top: -2px;\n}\n.modal-title {\n  margin: 0;\n  line-height: 1.42857143;\n}\n.modal-body {\n  position: relative;\n  padding: 15px;\n}\n.modal-footer {\n  padding: 15px;\n  text-align: right;\n  border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n  margin-bottom: 0;\n  margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n  margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n  margin-left: 0;\n}\n.modal-scrollbar-measure {\n  position: absolute;\n  top: -9999px;\n  width: 50px;\n  height: 50px;\n  overflow: scroll;\n}\n@media (min-width: 768px) {\n  .modal-dialog {\n    width: 600px;\n    margin: 30px auto;\n  }\n  .modal-content {\n    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n            box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n  }\n  .modal-sm {\n    width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg {\n    width: 900px;\n  }\n}\n.tooltip {\n  position: absolute;\n  z-index: 1070;\n  display: block;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 12px;\n  font-weight: normal;\n  line-height: 1.4;\n  visibility: visible;\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.tooltip.in {\n  filter: alpha(opacity=90);\n  opacity: .9;\n}\n.tooltip.top {\n  padding: 5px 0;\n  margin-top: -3px;\n}\n.tooltip.right {\n  padding: 0 5px;\n  margin-left: 3px;\n}\n.tooltip.bottom {\n  padding: 5px 0;\n  margin-top: 3px;\n}\n.tooltip.left {\n  padding: 0 5px;\n  margin-left: -3px;\n}\n.tooltip-inner {\n  max-width: 200px;\n  padding: 3px 8px;\n  color: #fff;\n  text-align: center;\n  text-decoration: none;\n  background-color: #000;\n  border-radius: 4px;\n}\n.tooltip-arrow {\n  position: absolute;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n  bottom: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n  right: 5px;\n  bottom: 0;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n  bottom: 0;\n  left: 5px;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n  top: 50%;\n  left: 0;\n  margin-top: -5px;\n  border-width: 5px 5px 5px 0;\n  border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n  top: 50%;\n  right: 0;\n  margin-top: -5px;\n  border-width: 5px 0 5px 5px;\n  border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n  top: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n  top: 0;\n  right: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n  top: 0;\n  left: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.popover {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1060;\n  display: none;\n  max-width: 276px;\n  padding: 1px;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: left;\n  white-space: normal;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n          box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n}\n.popover.top {\n  margin-top: -10px;\n}\n.popover.right {\n  margin-left: 10px;\n}\n.popover.bottom {\n  margin-top: 10px;\n}\n.popover.left {\n  margin-left: -10px;\n}\n.popover-title {\n  padding: 8px 14px;\n  margin: 0;\n  font-size: 14px;\n  background-color: #f7f7f7;\n  border-bottom: 1px solid #ebebeb;\n  border-radius: 5px 5px 0 0;\n}\n.popover-content {\n  padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n  position: absolute;\n  display: block;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.popover > .arrow {\n  border-width: 11px;\n}\n.popover > .arrow:after {\n  content: \"\";\n  border-width: 10px;\n}\n.popover.top > .arrow {\n  bottom: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-color: #999;\n  border-top-color: rgba(0, 0, 0, .25);\n  border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n  bottom: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-color: #fff;\n  border-bottom-width: 0;\n}\n.popover.right > .arrow {\n  top: 50%;\n  left: -11px;\n  margin-top: -11px;\n  border-right-color: #999;\n  border-right-color: rgba(0, 0, 0, .25);\n  border-left-width: 0;\n}\n.popover.right > .arrow:after {\n  bottom: -10px;\n  left: 1px;\n  content: \" \";\n  border-right-color: #fff;\n  border-left-width: 0;\n}\n.popover.bottom > .arrow {\n  top: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-width: 0;\n  border-bottom-color: #999;\n  border-bottom-color: rgba(0, 0, 0, .25);\n}\n.popover.bottom > .arrow:after {\n  top: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-width: 0;\n  border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n  top: 50%;\n  right: -11px;\n  margin-top: -11px;\n  border-right-width: 0;\n  border-left-color: #999;\n  border-left-color: rgba(0, 0, 0, .25);\n}\n.popover.left > .arrow:after {\n  right: 1px;\n  bottom: -10px;\n  content: \" \";\n  border-right-width: 0;\n  border-left-color: #fff;\n}\n.carousel {\n  position: relative;\n}\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner > .item {\n  position: relative;\n  display: none;\n  -webkit-transition: .6s ease-in-out left;\n       -o-transition: .6s ease-in-out left;\n          transition: .6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n  .carousel-inner > .item {\n    -webkit-transition: -webkit-transform .6s ease-in-out;\n         -o-transition:      -o-transform .6s ease-in-out;\n            transition:         transform .6s ease-in-out;\n\n    -webkit-backface-visibility: hidden;\n            backface-visibility: hidden;\n    -webkit-perspective: 1000;\n            perspective: 1000;\n  }\n  .carousel-inner > .item.next,\n  .carousel-inner > .item.active.right {\n    left: 0;\n    -webkit-transform: translate3d(100%, 0, 0);\n            transform: translate3d(100%, 0, 0);\n  }\n  .carousel-inner > .item.prev,\n  .carousel-inner > .item.active.left {\n    left: 0;\n    -webkit-transform: translate3d(-100%, 0, 0);\n            transform: translate3d(-100%, 0, 0);\n  }\n  .carousel-inner > .item.next.left,\n  .carousel-inner > .item.prev.right,\n  .carousel-inner > .item.active {\n    left: 0;\n    -webkit-transform: translate3d(0, 0, 0);\n            transform: translate3d(0, 0, 0);\n  }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  display: block;\n}\n.carousel-inner > .active {\n  left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.carousel-inner > .next {\n  left: 100%;\n}\n.carousel-inner > .prev {\n  left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n  left: 0;\n}\n.carousel-inner > .active.left {\n  left: -100%;\n}\n.carousel-inner > .active.right {\n  left: 100%;\n}\n.carousel-control {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 15%;\n  font-size: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.carousel-control.left {\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control.right {\n  right: 0;\n  left: auto;\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n  color: #fff;\n  text-decoration: none;\n  filter: alpha(opacity=90);\n  outline: 0;\n  opacity: .9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n  position: absolute;\n  top: 50%;\n  z-index: 5;\n  display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n  left: 50%;\n  margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n  right: 50%;\n  margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n  width: 20px;\n  height: 20px;\n  margin-top: -10px;\n  font-family: serif;\n  line-height: 1;\n}\n.carousel-control .icon-prev:before {\n  content: '\\2039';\n}\n.carousel-control .icon-next:before {\n  content: '\\203a';\n}\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  padding-left: 0;\n  margin-left: -30%;\n  text-align: center;\n  list-style: none;\n}\n.carousel-indicators li {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  margin: 1px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #000 \\9;\n  background-color: rgba(0, 0, 0, 0);\n  border: 1px solid #fff;\n  border-radius: 10px;\n}\n.carousel-indicators .active {\n  width: 12px;\n  height: 12px;\n  margin: 0;\n  background-color: #fff;\n}\n.carousel-caption {\n  position: absolute;\n  right: 15%;\n  bottom: 20px;\n  left: 15%;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n}\n.carousel-caption .btn {\n  text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-prev,\n  .carousel-control .icon-next {\n    width: 30px;\n    height: 30px;\n    margin-top: -15px;\n    font-size: 30px;\n  }\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .icon-prev {\n    margin-left: -15px;\n  }\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-next {\n    margin-right: -15px;\n  }\n  .carousel-caption {\n    right: 20%;\n    left: 20%;\n    padding-bottom: 30px;\n  }\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n  display: table;\n  content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n  clear: both;\n}\n.center-block {\n  display: block;\n  margin-right: auto;\n  margin-left: auto;\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  font: 0/0 a;\n  color: transparent;\n  text-shadow: none;\n  background-color: transparent;\n  border: 0;\n}\n.hidden {\n  display: none !important;\n  visibility: hidden !important;\n}\n.affix {\n  position: fixed;\n}\n@-ms-viewport {\n  width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n  display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n  display: none !important;\n}\n@media (max-width: 767px) {\n  .visible-xs {\n    display: block !important;\n  }\n  table.visible-xs {\n    display: table;\n  }\n  tr.visible-xs {\n    display: table-row !important;\n  }\n  th.visible-xs,\n  td.visible-xs {\n    display: table-cell !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-block {\n    display: block !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline {\n    display: inline !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm {\n    display: block !important;\n  }\n  table.visible-sm {\n    display: table;\n  }\n  tr.visible-sm {\n    display: table-row !important;\n  }\n  th.visible-sm,\n  td.visible-sm {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-block {\n    display: block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md {\n    display: block !important;\n  }\n  table.visible-md {\n    display: table;\n  }\n  tr.visible-md {\n    display: table-row !important;\n  }\n  th.visible-md,\n  td.visible-md {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-block {\n    display: block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg {\n    display: block !important;\n  }\n  table.visible-lg {\n    display: table;\n  }\n  tr.visible-lg {\n    display: table-row !important;\n  }\n  th.visible-lg,\n  td.visible-lg {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-block {\n    display: block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (max-width: 767px) {\n  .hidden-xs {\n    display: none !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .hidden-sm {\n    display: none !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .hidden-md {\n    display: none !important;\n  }\n}\n@media (min-width: 1200px) {\n  .hidden-lg {\n    display: none !important;\n  }\n}\n.visible-print {\n  display: none !important;\n}\n@media print {\n  .visible-print {\n    display: block !important;\n  }\n  table.visible-print {\n    display: table;\n  }\n  tr.visible-print {\n    display: table-row !important;\n  }\n  th.visible-print,\n  td.visible-print {\n    display: table-cell !important;\n  }\n}\n.visible-print-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-block {\n    display: block !important;\n  }\n}\n.visible-print-inline {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline {\n    display: inline !important;\n  }\n}\n.visible-print-inline-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline-block {\n    display: inline-block !important;\n  }\n}\n@media print {\n  .hidden-print {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap.css.map */\n"
  },
  {
    "path": "css/hux-blog.css",
    "content": "@media (min-width: 1200px) {\n  .post-container,\n  .sidebar-container {\n    padding-right: 5%;\n  }\n}\n@media (min-width: 768px) {\n  .post-container {\n    padding-right: 5%;\n  }\n}\n.sidebar-container {\n  color: #bfbfbf;\n  font-size: 14px;\n}\n.sidebar-container h5 {\n  color: #808080;\n  padding-bottom: 1em;\n}\n.sidebar-container h5 a {\n  color: #808080 !important;\n  text-decoration: none;\n}\n.sidebar-container a {\n  color: #bfbfbf !important;\n}\n.sidebar-container a:hover,\n.sidebar-container a:active {\n  color: #0085a1 !important;\n}\n.sidebar-container .tags a {\n  border-color: #bfbfbf;\n}\n.sidebar-container .tags a:hover,\n.sidebar-container .tags a:active {\n  border-color: #0085a1;\n}\n.sidebar-container .short-about img {\n  width: 80%;\n  display: block;\n  border-radius: 5px;\n  margin-bottom: 20px;\n}\n.sidebar-container .short-about p {\n  margin-top: 0px;\n  margin-bottom: 20px;\n}\n.sidebar-container .short-about .list-inline > li {\n  padding-left: 0px;\n}\n.catalog-container {\n  padding: 0px;\n}\n.side-catalog {\n  display: block;\n  overflow: auto;\n  height: 100%;\n  padding-bottom: 40px;\n  width: 195px;\n}\n.side-catalog.fixed {\n  position: fixed;\n  top: -21px;\n}\n.side-catalog.fold .catalog-toggle::before {\n  content: \"+\";\n}\n.side-catalog.fold .catalog-body {\n  display: none;\n}\n.side-catalog .catalog-toggle::before {\n  content: \"−\";\n  position: relative;\n  margin-right: 5px;\n  bottom: 1px;\n}\n.side-catalog .catalog-body {\n  position: relative;\n  list-style: none;\n  height: auto;\n  overflow: hidden;\n  padding-left: 0px;\n  padding-right: 5px;\n  text-indent: 0;\n}\n.side-catalog .catalog-body li {\n  position: relative;\n  list-style: none;\n}\n.side-catalog .catalog-body li a {\n  padding-left: 10px;\n  max-width: 180px;\n  display: inline-block;\n  vertical-align: middle;\n  height: 30px;\n  line-height: 30px;\n  overflow: hidden;\n  text-decoration: none;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.side-catalog .catalog-body .h1_nav,\n.side-catalog .catalog-body .h2_nav,\n.side-catalog .catalog-body .h3_nav {\n  margin-left: 0;\n  font-size: 13px;\n  font-weight: bold;\n}\n.side-catalog .catalog-body .h4_nav,\n.side-catalog .catalog-body .h5_nav,\n.side-catalog .catalog-body .h6_nav {\n  margin-left: 10px;\n  font-size: 12px;\n}\n.side-catalog .catalog-body .h4_nav a,\n.side-catalog .catalog-body .h5_nav a,\n.side-catalog .catalog-body .h6_nav a {\n  max-width: 170px;\n}\n.side-catalog .catalog-body .active {\n  border-radius: 4px;\n  background-color: #F5F5F5;\n}\n.side-catalog .catalog-body .active a {\n  color: #0085a1!important;\n}\n@media (max-width: 1200px) {\n  .side-catalog {\n    display: none;\n  }\n}\nbody {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  font-size: 16px;\n  color: #404040;\n  overflow-x: hidden;\n}\np {\n  margin: 30px 0;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  line-height: 1.1;\n  font-weight: bold;\n}\nh4 {\n  font-size: 21px;\n}\na {\n  color: #404040;\n}\na:hover,\na:focus {\n  color: #0085a1;\n}\na img:hover,\na img:focus {\n  cursor: zoom-in;\n}\narticle {\n  overflow-x: hidden;\n}\nblockquote {\n  color: #808080;\n  font-style: italic;\n  font-size: 0.95em;\n  margin: 20px 0 20px;\n}\nblockquote p {\n  margin: 0;\n}\nsmall.img-hint {\n  display: block;\n  margin-top: -20px;\n  text-align: center;\n}\nbr + small.img-hint {\n  margin-top: -40px;\n}\nimg.shadow {\n  box-shadow: rgba(0, 0, 0, 0.258824) 0px 2px 5px 0px;\n}\nselect {\n  -webkit-appearance: none;\n  margin-top: 15px;\n  color: #337ab7;\n  border-color: #337ab7;\n  padding: 0em 0.4em;\n  background: white;\n}\nselect.sel-lang {\n  min-height: 28px;\n  font-size: 14px;\n}\n.table th,\n.table td {\n  border: 1px solid #eee !important;\n}\nhr.small {\n  max-width: 100px;\n  margin: 15px auto;\n  border-width: 4px;\n  border-color: white;\n}\npre,\n.table-responsive {\n  -webkit-overflow-scrolling: touch;\n}\npre code {\n  display: block;\n  width: auto;\n  white-space: pre;\n  word-wrap: normal;\n}\n.postlist-container {\n  margin-bottom: 15px;\n}\n.post-container a {\n  color: #337ab7;\n}\n.post-container a:hover,\n.post-container a:focus {\n  color: #0085a1;\n}\n.post-container h1,\n.post-container h2,\n.post-container h3,\n.post-container h4,\n.post-container h5,\n.post-container h6 {\n  margin: 30px 0 10px;\n}\n.post-container h5 {\n  font-size: 19px;\n  font-weight: 600;\n  color: gray;\n}\n.post-container h5 + p {\n  margin-top: 5px;\n}\n.post-container h6 {\n  font-size: 16px;\n  font-weight: 600;\n  color: gray;\n}\n.post-container h6 + p {\n  margin-top: 5px;\n}\n.post-container ul,\n.post-container ol {\n  margin-bottom: 40px;\n}\n@media screen and (max-width: 768px) {\n  .post-container ul,\n  .post-container ol {\n    padding-left: 30px;\n  }\n}\n@media screen and (max-width: 500px) {\n  .post-container ul,\n  .post-container ol {\n    padding-left: 20px;\n  }\n}\n.post-container ol ol,\n.post-container ol ul,\n.post-container ul ol,\n.post-container ul ul {\n  margin-bottom: 5px;\n}\n.post-container li p {\n  margin: 0;\n  margin-bottom: 5px;\n}\n.post-container li h1,\n.post-container li h2,\n.post-container li h3,\n.post-container li h4,\n.post-container li h5,\n.post-container li h6 {\n  line-height: 2;\n  margin-top: 20px;\n}\n.post-container .pager li {\n  width: 48%;\n}\n.post-container .pager li.next {\n  float: right;\n}\n.post-container .pager li.previous {\n  float: left;\n}\n.post-container .pager li > a {\n  width: 100%;\n}\n.post-container .pager li > a > span {\n  color: #808080;\n  font-weight: normal;\n  letter-spacing: 0.5px;\n}\n@media only screen and (max-width: 767px) {\n  /**\n\t * Layout\n\t * Since V1.6 we use absolute positioning to prevent to expand container-fluid\n\t * which would cover tags. A absolute positioning make a new layer.\n\t */\n  .navbar-default .navbar-collapse {\n    position: absolute;\n    right: 0;\n    border: none;\n    background: white;\n    box-shadow: 0px 5px 10px 2px rgba(0, 0, 0, 0.2);\n    box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.239216) 0px 1px 4px;\n    border-radius: 2px;\n    width: 170px;\n  }\n  /**\n\t * Animation\n\t * HuxBlog-Navbar using genuine Material Design Animation\n\t */\n  #huxblog_navbar {\n    /**\n\t\t * Sharable code and 'out' function\n\t\t */\n    opacity: 0;\n    transform: scaleX(0);\n    transform-origin: top right;\n    transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n    -webkit-transform: scaleX(0);\n    -webkit-transform-origin: top right;\n    -webkit-transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n    /**\n\t\t *'In' Animation\n\t\t */\n  }\n  #huxblog_navbar a {\n    font-size: 13px;\n    line-height: 28px;\n  }\n  #huxblog_navbar .navbar-collapse {\n    height: 0px;\n    transform: scaleY(0);\n    transform-origin: top right;\n    transition: transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n    -webkit-transform: scaleY(0);\n    -webkit-transform-origin: top right;\n    -webkit-transition: -webkit-transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n  }\n  #huxblog_navbar li {\n    opacity: 0;\n    transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    -webkit-transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n  }\n  #huxblog_navbar.in {\n    transform: scaleX(1);\n    -webkit-transform: scaleX(1);\n    opacity: 1;\n    transition: all 250ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    -webkit-transition: all 250ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n  }\n  #huxblog_navbar.in .navbar-collapse {\n    transform: scaleY(1);\n    -webkit-transform: scaleY(1);\n    transition: transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n    -webkit-transition: -webkit-transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n  }\n  #huxblog_navbar.in li {\n    opacity: 1;\n    transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n    -webkit-transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n  }\n}\n.navbar-custom {\n  background: none;\n  border: none;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  z-index: 3;\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n}\n.navbar-custom .navbar-brand {\n  font-weight: 800;\n  color: white;\n  height: 56px;\n  line-height: 25px;\n}\n.navbar-custom .navbar-brand:hover {\n  color: rgba(255, 255, 255, 0.8);\n}\n.navbar-custom .nav li a {\n  text-transform: uppercase;\n  font-size: 12px;\n  line-height: 20px;\n  font-weight: 800;\n  letter-spacing: 1px;\n}\n.navbar-custom .nav li a:active {\n  background: rgba(0, 0, 0, 0.12);\n}\n@media only screen and (min-width: 768px) {\n  .navbar-custom {\n    background: transparent;\n    border-bottom: 1px solid transparent;\n  }\n  .navbar-custom body {\n    font-size: 20px;\n  }\n  .navbar-custom .navbar-brand {\n    color: white;\n    padding: 20px;\n    line-height: 20px;\n  }\n  .navbar-custom .navbar-brand:hover,\n  .navbar-custom .navbar-brand:focus {\n    color: rgba(255, 255, 255, 0.8);\n  }\n  .navbar-custom .nav li a {\n    color: white;\n    padding: 20px;\n  }\n  .navbar-custom .nav li a:hover,\n  .navbar-custom .nav li a:focus {\n    color: rgba(255, 255, 255, 0.8);\n  }\n  .navbar-custom .nav li a:active {\n    background: none;\n  }\n}\n@media only screen and (min-width: 1170px) {\n  .navbar-custom {\n    -webkit-transition: background-color 0.3s;\n    -moz-transition: background-color 0.3s;\n    transition: background-color 0.3s;\n    /* Force Hardware Acceleration in WebKit */\n    -webkit-transform: translate3d(0, 0, 0);\n    -moz-transform: translate3d(0, 0, 0);\n    -ms-transform: translate3d(0, 0, 0);\n    -o-transform: translate3d(0, 0, 0);\n    transform: translate3d(0, 0, 0);\n    -webkit-backface-visibility: hidden;\n    backface-visibility: hidden;\n  }\n  .navbar-custom.is-fixed {\n    /* when the user scrolls down, we hide the header right above the viewport */\n    position: fixed;\n    top: -61px;\n    background-color: rgba(255, 255, 255, 0.9);\n    border-bottom: 1px solid #f2f2f2;\n    -webkit-transition: -webkit-transform 0.3s;\n    -moz-transition: -moz-transform 0.3s;\n    transition: transform 0.3s;\n  }\n  .navbar-custom.is-fixed .navbar-brand {\n    color: #404040;\n  }\n  .navbar-custom.is-fixed .navbar-brand:hover,\n  .navbar-custom.is-fixed .navbar-brand:focus {\n    color: #0085a1;\n  }\n  .navbar-custom.is-fixed .nav li a {\n    color: #404040;\n  }\n  .navbar-custom.is-fixed .nav li a:hover,\n  .navbar-custom.is-fixed .nav li a:focus {\n    color: #0085a1;\n  }\n  .navbar-custom.is-visible {\n    /* if the user changes the scrolling direction, we show the header */\n    -webkit-transform: translate3d(0, 100%, 0);\n    -moz-transform: translate3d(0, 100%, 0);\n    -ms-transform: translate3d(0, 100%, 0);\n    -o-transform: translate3d(0, 100%, 0);\n    transform: translate3d(0, 100%, 0);\n  }\n}\n.intro-header {\n  background: no-repeat center center;\n  background-color: #808080;\n  background-attachment: scroll;\n  -webkit-background-size: cover;\n  -moz-background-size: cover;\n  background-size: cover;\n  -o-background-size: cover;\n  margin-bottom: 0px;\n  /* 0 on mobile, modify by Hux */\n}\n@media only screen and (min-width: 768px) {\n  .intro-header {\n    margin-bottom: 20px;\n    /* response on desktop */\n  }\n}\n.intro-header .site-heading,\n.intro-header .post-heading,\n.intro-header .page-heading {\n  padding: 85px 0 55px;\n  color: white;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .site-heading,\n  .intro-header .post-heading,\n  .intro-header .page-heading {\n    padding: 150px 0;\n  }\n}\n.intro-header .site-heading {\n  padding: 95px 0 70px;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .site-heading {\n    padding: 150px 0;\n  }\n}\n.intro-header .site-heading,\n.intro-header .page-heading {\n  text-align: center;\n}\n.intro-header .site-heading h1,\n.intro-header .page-heading h1 {\n  margin-top: 0;\n  font-size: 50px;\n}\n.intro-header .site-heading .subheading,\n.intro-header .page-heading .subheading {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  font-size: 18px;\n  line-height: 1.1;\n  display: block;\n  font-weight: 300;\n  margin: 10px 0 0;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .site-heading h1,\n  .intro-header .page-heading h1 {\n    font-size: 80px;\n  }\n}\n.intro-header .post-heading h1 {\n  font-size: 30px;\n  margin-bottom: 24px;\n}\n.intro-header .post-heading .subheading,\n.intro-header .post-heading .meta {\n  line-height: 1.1;\n  display: block;\n}\n.intro-header .post-heading .subheading {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  font-size: 17px;\n  line-height: 1.4;\n  font-weight: normal;\n  margin: 10px 0 30px;\n  margin-top: -5px;\n}\n.intro-header .post-heading .meta {\n  font-family: 'Lora', 'Times New Roman', serif;\n  font-style: italic;\n  font-weight: 300;\n  font-size: 18px;\n}\n.intro-header .post-heading .meta a {\n  color: white;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .post-heading h1 {\n    font-size: 55px;\n  }\n  .intro-header .post-heading .subheading {\n    font-size: 30px;\n  }\n  .intro-header .post-heading .meta {\n    font-size: 20px;\n  }\n}\n.post-preview > a {\n  color: #404040;\n}\n.post-preview > a:hover,\n.post-preview > a:focus {\n  text-decoration: none;\n  color: #0085a1;\n}\n.post-preview > a > .post-title {\n  font-size: 21px;\n  line-height: 1.3;\n  margin-top: 30px;\n  margin-bottom: 8px;\n}\n.post-preview > a > .post-subtitle {\n  font-size: 15px;\n  line-height: 1.3;\n  margin: 0;\n  font-weight: 300;\n  margin-bottom: 10px;\n}\n.post-preview > .post-meta {\n  font-family: 'Lora', 'Times New Roman', serif;\n  color: #808080;\n  font-size: 16px;\n  font-style: italic;\n  margin-top: 0;\n}\n.post-preview > .post-meta > a {\n  text-decoration: none;\n  color: #404040;\n}\n.post-preview > .post-meta > a:hover,\n.post-preview > .post-meta > a:focus {\n  color: #0085a1;\n  text-decoration: underline;\n}\n@media only screen and (min-width: 768px) {\n  .post-preview > a > .post-title {\n    font-size: 26px;\n    line-height: 1.3;\n    margin-bottom: 10px;\n  }\n  .post-preview > a > .post-subtitle {\n    font-size: 16px;\n  }\n  .post-preview .post-meta {\n    font-size: 18px;\n  }\n}\n.post-content-preview {\n  font-size: 13px;\n  font-style: italic;\n  color: #a3a3a3;\n}\n.post-content-preview:hover {\n  color: #0085a1;\n}\n@media only screen and (min-width: 768px) {\n  .post-content-preview {\n    font-size: 14px;\n  }\n}\n.section-heading {\n  font-size: 36px;\n  margin-top: 60px;\n  font-weight: 700;\n}\n.caption {\n  text-align: center;\n  font-size: 14px;\n  padding: 10px;\n  font-style: italic;\n  margin: 0;\n  display: block;\n  border-bottom-right-radius: 5px;\n  border-bottom-left-radius: 5px;\n}\nfooter {\n  font-size: 20px;\n  padding: 50px 0 65px;\n}\nfooter .list-inline {\n  margin: 0;\n  padding: 0;\n}\nfooter .copyright {\n  font-size: 14px;\n  text-align: center;\n  margin-bottom: 0;\n}\nfooter .copyright a {\n  color: #337ab7;\n}\nfooter .copyright a:hover,\nfooter .copyright a:focus {\n  color: #0085a1;\n}\n.floating-label-form-group {\n  font-size: 14px;\n  position: relative;\n  margin-bottom: 0;\n  padding-bottom: 0.5em;\n  border-bottom: 1px solid #eeeeee;\n}\n.floating-label-form-group input,\n.floating-label-form-group textarea {\n  z-index: 1;\n  position: relative;\n  padding-right: 0;\n  padding-left: 0;\n  border: none;\n  border-radius: 0;\n  font-size: 1.5em;\n  background: none;\n  box-shadow: none !important;\n  resize: none;\n}\n.floating-label-form-group label {\n  display: block;\n  z-index: 0;\n  position: relative;\n  top: 2em;\n  margin: 0;\n  font-size: 0.85em;\n  line-height: 1.764705882em;\n  vertical-align: middle;\n  vertical-align: baseline;\n  opacity: 0;\n  -webkit-transition: top 0.3s ease,opacity 0.3s ease;\n  -moz-transition: top 0.3s ease,opacity 0.3s ease;\n  -ms-transition: top 0.3s ease,opacity 0.3s ease;\n  transition: top 0.3s ease,opacity 0.3s ease;\n}\n.floating-label-form-group::not(:first-child) {\n  padding-left: 14px;\n  border-left: 1px solid #eeeeee;\n}\n.floating-label-form-group-with-value label {\n  top: 0;\n  opacity: 1;\n}\n.floating-label-form-group-with-focus label {\n  color: #0085a1;\n}\nform .row:first-child .floating-label-form-group {\n  border-top: 1px solid #eeeeee;\n}\n.btn {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  text-transform: uppercase;\n  font-size: 14px;\n  font-weight: 800;\n  letter-spacing: 1px;\n  border-radius: 0;\n  padding: 15px 25px;\n}\n.btn-lg {\n  font-size: 16px;\n  padding: 25px 35px;\n}\n.btn-default:hover,\n.btn-default:focus {\n  background-color: #0085a1;\n  border: 1px solid #0085a1;\n  color: white;\n}\n.pager {\n  margin: 20px 0 0 !important;\n  padding: 0px !important;\n}\n.pager li > a,\n.pager li > span {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  text-transform: uppercase;\n  font-size: 13px;\n  font-weight: 800;\n  letter-spacing: 1px;\n  padding: 10px;\n  background-color: white;\n  border-radius: 0;\n}\n@media only screen and (min-width: 768px) {\n  .pager li > a,\n  .pager li > span {\n    font-size: 14px;\n    padding: 15px 25px;\n  }\n}\n.pager li > a {\n  color: #404040;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  color: white;\n  background-color: #0085a1;\n  border: 1px solid #0085a1;\n}\n.pager li > a:hover > span,\n.pager li > a:focus > span {\n  color: white;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #808080;\n  background-color: #404040;\n  cursor: not-allowed;\n}\n::-moz-selection {\n  color: white;\n  text-shadow: none;\n  background: #0085a1;\n}\n::selection {\n  color: white;\n  text-shadow: none;\n  background: #0085a1;\n}\nimg::selection {\n  color: white;\n  background: transparent;\n}\nimg::-moz-selection {\n  color: white;\n  background: transparent;\n}\n/* Hux add tags support */\n.tags {\n  margin-bottom: -5px;\n}\n.tags a,\n.tags .tag {\n  display: inline-block;\n  border: 1px solid rgba(255, 255, 255, 0.8);\n  border-radius: 999em;\n  padding: 0 10px;\n  color: #ffffff;\n  line-height: 24px;\n  font-size: 12px;\n  text-decoration: none;\n  margin: 0 1px;\n  margin-bottom: 6px;\n}\n.tags a:hover,\n.tags .tag:hover,\n.tags a:active,\n.tags .tag:active {\n  color: white;\n  border-color: white;\n  background-color: rgba(255, 255, 255, 0.4);\n  text-decoration: none;\n}\n@media only screen and (min-width: 768px) {\n  .tags a,\n  .tags .tag {\n    margin-right: 5px;\n  }\n}\n#tag-heading {\n  padding: 70px 0 60px;\n}\n@media only screen and (min-width: 768px) {\n  #tag-heading {\n    padding: 55px 0;\n  }\n}\n#tag_cloud {\n  margin: 20px 0 15px 0;\n}\n#tag_cloud a,\n#tag_cloud .tag {\n  font-size: 14px;\n  border: none;\n  line-height: 28px;\n  margin: 0 2px;\n  margin-bottom: 8px;\n  background: #D6D6D6;\n}\n#tag_cloud a:hover,\n#tag_cloud .tag:hover,\n#tag_cloud a:active,\n#tag_cloud .tag:active {\n  background-color: #0085a1 !important;\n}\n@media only screen and (min-width: 768px) {\n  #tag_cloud {\n    margin-bottom: 25px;\n  }\n}\n.tag-comments {\n  font-size: 12px;\n}\n@media only screen and (min-width: 768px) {\n  .tag-comments {\n    font-size: 14px;\n  }\n}\n.t:first-child {\n  margin-top: 0px;\n}\n.listing-seperator {\n  color: #0085a1;\n  font-size: 21px !important;\n}\n.listing-seperator::before {\n  margin-right: 5px;\n}\n@media only screen and (min-width: 768px) {\n  .listing-seperator {\n    font-size: 20px !important;\n    line-height: 2 !important;\n  }\n}\n.one-tag-list .tag-text {\n  font-weight: 200;\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n}\n.one-tag-list .post-preview {\n  position: relative;\n}\n.one-tag-list .post-preview > a .post-title {\n  font-size: 16px;\n  font-weight: 500;\n  margin-top: 20px;\n}\n.one-tag-list .post-preview > a .post-subtitle {\n  font-size: 12px;\n}\n.one-tag-list .post-preview > .post-meta {\n  position: absolute;\n  right: 5px;\n  bottom: 0px;\n  margin: 0px;\n  font-size: 12px;\n  line-height: 12px;\n}\n@media only screen and (min-width: 768px) {\n  .one-tag-list .post-preview {\n    margin-left: 20px;\n  }\n  .one-tag-list .post-preview > a > .post-title {\n    font-size: 18px;\n    line-height: 1.3;\n  }\n  .one-tag-list .post-preview > a > .post-subtitle {\n    font-size: 14px;\n  }\n  .one-tag-list .post-preview .post-meta {\n    font-size: 18px;\n  }\n}\n/* Tags support End*/\n/* Hux make all img responsible in post-container */\n.post-container img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n  margin: 1.5em auto 1.6em auto;\n}\n/* Hux Optimize UserExperience */\n.navbar-default .navbar-toggle:focus,\n.navbar-default .navbar-toggle:hover {\n  background-color: inherit;\n}\n.navbar-default .navbar-toggle:active {\n  background-color: rgba(255, 255, 255, 0.25);\n}\n/* Hux customize Style for navBar button */\n.navbar-default .navbar-toggle {\n  border-color: transparent;\n  padding: 19px 16px;\n  margin-top: 2px;\n  margin-right: 2px;\n  margin-bottom: 2px;\n  border-radius: 50%;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  width: 18px;\n  border-radius: 0px;\n  background-color: white;\n}\n.navbar-default .navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 3px;\n}\n/* Hux customize Style for Duoshuo */\n.comment {\n  margin-top: 20px;\n}\n.comment #ds-thread #ds-reset a.ds-like-thread-button {\n  border: 1px solid #ddd;\n  border-radius: 0px;\n  background: white;\n  box-shadow: none;\n  text-shadow: none;\n}\n.comment #ds-thread #ds-reset li.ds-tab a.ds-current {\n  border: 1px solid #ddd;\n  border-radius: 0px;\n  background: white;\n  box-shadow: none;\n  text-shadow: none;\n}\n.comment #ds-thread #ds-reset .ds-textarea-wrapper {\n  background: none;\n}\n.comment #ds-thread #ds-reset .ds-gradient-bg {\n  background: none;\n}\n.comment #ds-thread #ds-reset .ds-post-options {\n  border-bottom: 1px solid #ccc;\n}\n.comment #ds-thread #ds-reset .ds-post-button {\n  border-bottom: 1px solid #ccc;\n}\n.comment #ds-thread #ds-reset .ds-post-button {\n  background: white;\n  box-shadow: none;\n}\n.comment #ds-thread #ds-reset .ds-post-button:hover {\n  background: #eeeeee;\n}\n#ds-smilies-tooltip ul.ds-smilies-tabs li a {\n  background: white !important;\n}\n.page-fullscreen .intro-header {\n  position: fixed;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n}\n.page-fullscreen #tag-heading {\n  position: fixed;\n  left: 0;\n  top: 0;\n  padding-bottom: 150px;\n  width: 100%;\n  height: 100%;\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-box-pack: center;\n  -webkit-box-align: center;\n  display: -webkit-flex;\n  -webkit-align-items: center;\n  -webkit-justify-content: center;\n  -webkit-flex-direction: column;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n}\n.page-fullscreen footer {\n  position: absolute;\n  width: 100%;\n  bottom: 0;\n  padding-bottom: 20px;\n  opacity: 0.6;\n  color: #fff;\n}\n.page-fullscreen footer .copyright {\n  color: #fff;\n}\n.page-fullscreen footer .copyright a {\n  color: #fff;\n}\n.page-fullscreen footer .copyright a:hover {\n  color: #ddd;\n}\n"
  },
  {
    "path": "css/syntax.css",
    "content": "/* to make lines scroll instead of wrap */\n/* from http://stackoverflow.com/a/23393920 */\n\n.highlight pre code * {\n  white-space: nowrap;    // this sets all children inside to nowrap\n}\n\n.highlight pre {\n  overflow-x: auto;       // this sets the scrolling in x\n}\n\n.highlight pre code {\n  white-space: pre;       // forces <code> to respect <pre> formatting\n}\n\n\n/*\n * GitHub style for Pygments syntax highlighter, for use with Jekyll\n * Courtesy of GitHub.com\n */\n\n.highlight pre, pre, .highlight .hll { background-color: #f8f8f8; border: 1px solid #ccc; padding: 6px 10px; border-radius: 3px; }\n.highlight .c { color: #999988; font-style: italic; }\n.highlight .err { color: #a61717; background-color: #e3d2d2; }\n.highlight .k { font-weight: bold; }\n.highlight .o { font-weight: bold; }\n.highlight .cm { color: #999988; font-style: italic; }\n.highlight .cp { color: #999999; font-weight: bold; }\n.highlight .c1 { color: #999988; font-style: italic; }\n.highlight .cs { color: #999999; font-weight: bold; font-style: italic; }\n.highlight .gd { color: #000000; background-color: #ffdddd; }\n.highlight .gd .x { color: #000000; background-color: #ffaaaa; }\n.highlight .ge { font-style: italic; }\n.highlight .gr { color: #aa0000; }\n.highlight .gh { color: #999999; }\n.highlight .gi { color: #000000; background-color: #ddffdd; }\n.highlight .gi .x { color: #000000; background-color: #aaffaa; }\n.highlight .go { color: #888888; }\n.highlight .gp { color: #555555; }\n.highlight .gs { font-weight: bold; }\n.highlight .gu { color: #800080; font-weight: bold; }\n.highlight .gt { color: #aa0000; }\n.highlight .kc { font-weight: bold; }\n.highlight .kd { font-weight: bold; }\n.highlight .kn { font-weight: bold; }\n.highlight .kp { font-weight: bold; }\n.highlight .kr { font-weight: bold; }\n.highlight .kt { color: #445588; font-weight: bold; }\n.highlight .m { color: #009999; }\n.highlight .s { color: #dd1144; }\n.highlight .n { color: #333333; }\n.highlight .na { color: teal; }\n.highlight .nb { color: #0086b3; }\n.highlight .nc { color: #445588; font-weight: bold; }\n.highlight .no { color: teal; }\n.highlight .ni { color: purple; }\n.highlight .ne { color: #990000; font-weight: bold; }\n.highlight .nf { color: #990000; font-weight: bold; }\n.highlight .nn { color: #555555; }\n.highlight .nt { color: navy; }\n.highlight .nv { color: teal; }\n.highlight .ow { font-weight: bold; }\n.highlight .w { color: #bbbbbb; }\n.highlight .mf { color: #009999; }\n.highlight .mh { color: #009999; }\n.highlight .mi { color: #009999; }\n.highlight .mo { color: #009999; }\n.highlight .sb { color: #dd1144; }\n.highlight .sc { color: #dd1144; }\n.highlight .sd { color: #dd1144; }\n.highlight .s2 { color: #dd1144; }\n.highlight .se { color: #dd1144; }\n.highlight .sh { color: #dd1144; }\n.highlight .si { color: #dd1144; }\n.highlight .sx { color: #dd1144; }\n.highlight .sr { color: #009926; }\n.highlight .s1 { color: #dd1144; }\n.highlight .ss { color: #990073; }\n.highlight .bp { color: #999999; }\n.highlight .vc { color: teal; }\n.highlight .vg { color: teal; }\n.highlight .vi { color: teal; }\n.highlight .il { color: #009999; }\n.highlight .gc { color: #999; background-color: #EAF2F5; }\n"
  },
  {
    "path": "feed.xml",
    "content": "---\nlayout: null\n---\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n  <channel>\n    <title>{{ site.title | xml_escape }}</title>\n    <description>{{ site.description | xml_escape }}</description>\n    <link>{{ site.url }}{{ site.baseurl }}/</link>\n    <atom:link href=\"{{ \"/feed.xml\" | prepend: site.baseurl | prepend: site.url }}\" rel=\"self\" type=\"application/rss+xml\" />\n    <pubDate>{{ site.time | date_to_rfc822 }}</pubDate>\n    <lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>\n    <generator>Jekyll v{{ jekyll.version }}</generator>\n    {% for post in site.posts limit:10 %}\n      <item>\n        <title>{{ post.title | xml_escape }}</title>\n        <description>{{ post.content | xml_escape }}</description>\n        <pubDate>{{ post.date | date_to_rfc822 }}</pubDate>\n        <link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>\n        <guid isPermaLink=\"true\">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>\n        {% for tag in post.tags %}\n        <category>{{ tag | xml_escape }}</category>\n        {% endfor %}\n        {% for cat in post.categories %}\n        <category>{{ cat | xml_escape }}</category>\n        {% endfor %}\n      </item>\n    {% endfor %}\n  </channel>\n</rss>\n"
  },
  {
    "path": "index.html",
    "content": "---\nlayout: page\ndescription: \"Power BI生命管理大师\"\n---\n\n{% for post in paginator.posts %}\n<div class=\"post-preview\">\n    <a href=\"{{ post.url | prepend: site.baseurl }}\">\n        <h2 class=\"post-title\">\n            {{ post.title }}\n        </h2>\n        {% if post.subtitle %}\n        <h3 class=\"post-subtitle\">\n            {{ post.subtitle }}\n        </h3>\n        {% endif %}\n        <div class=\"post-content-preview\">\n            {{ post.content | strip_html | truncate:200 }}\n        </div>\n    </a>\n    <p class=\"post-meta\">\n        Posted by {% if post.author %}{{ post.author }}{% else %}{{ site.title }}{% endif %} on {{ post.date | date: \"%B %-d, %Y\" }}\n    </p>\n</div>\n<hr>\n{% endfor %}\n\n<!-- Pager -->\n{% if paginator.total_pages > 1 %}\n<ul class=\"pager\">\n    {% if paginator.previous_page %}\n    <li class=\"previous\">\n        <a href=\"{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}\">&larr; Newer Posts</a>\n    </li>\n    {% endif %}\n    {% if paginator.next_page %}\n    <li class=\"next\">\n        <a href=\"{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}\">Older Posts &rarr;</a>\n    </li>\n    {% endif %}\n</ul>\n{% endif %}\n"
  },
  {
    "path": "js/bootstrap.js",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\nif (typeof jQuery === 'undefined') {\n  throw new Error('Bootstrap\\'s JavaScript requires jQuery')\n}\n\n+function ($) {\n  'use strict';\n  var version = $.fn.jquery.split(' ')[0].split('.')\n  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {\n    throw new Error('Bootstrap\\'s JavaScript requires jQuery version 1.9.1 or higher')\n  }\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: transition.js v3.3.2\n * http://getbootstrap.com/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      WebkitTransition : 'webkitTransitionEnd',\n      MozTransition    : 'transitionend',\n      OTransition      : 'oTransitionEnd otransitionend',\n      transition       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n\n    return false // explicit for ie8 (  ._.)\n  }\n\n  // http://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false\n    var $el = this\n    $(this).one('bsTransitionEnd', function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n\n    if (!$.support.transition) return\n\n    $.event.special.bsTransitionEnd = {\n      bindType: $.support.transition.end,\n      delegateType: $.support.transition.end,\n      handle: function (e) {\n        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n      }\n    }\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: alert.js v3.3.2\n * http://getbootstrap.com/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.VERSION = '3.3.2'\n\n  Alert.TRANSITION_DURATION = 150\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = $(selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.closest('.alert')\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      // detach from parent, fire event then clean up data\n      $parent.detach().trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one('bsTransitionEnd', removeElement)\n        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.alert\n\n  $.fn.alert             = Plugin\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: button.js v3.3.2\n * http://getbootstrap.com/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element  = $(element)\n    this.options   = $.extend({}, Button.DEFAULTS, options)\n    this.isLoading = false\n  }\n\n  Button.VERSION  = '3.3.2'\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state = state + 'Text'\n\n    if (data.resetText == null) $el.data('resetText', $el[val]())\n\n    // push to event loop to allow forms to submit\n    setTimeout($.proxy(function () {\n      $el[val](data[state] == null ? this.options[state] : data[state])\n\n      if (state == 'loadingText') {\n        this.isLoading = true\n        $el.addClass(d).attr(d, d)\n      } else if (this.isLoading) {\n        this.isLoading = false\n        $el.removeClass(d).removeAttr(d)\n      }\n    }, this), 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var changed = true\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n      if ($input.prop('type') == 'radio') {\n        if ($input.prop('checked') && this.$element.hasClass('active')) changed = false\n        else $parent.find('.active').removeClass('active')\n      }\n      if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')\n    } else {\n      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n    }\n\n    if (changed) this.$element.toggleClass('active')\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  var old = $.fn.button\n\n  $.fn.button             = Plugin\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document)\n    .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      var $btn = $(e.target)\n      if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')\n      Plugin.call($btn, 'toggle')\n      e.preventDefault()\n    })\n    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n    })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: carousel.js v3.3.2\n * http://getbootstrap.com/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      =\n    this.sliding     =\n    this.interval    =\n    this.$active     =\n    this.$items      = null\n\n    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n  }\n\n  Carousel.VERSION  = '3.3.2'\n\n  Carousel.TRANSITION_DURATION = 600\n\n  Carousel.DEFAULTS = {\n    interval: 5000,\n    pause: 'hover',\n    wrap: true,\n    keyboard: true\n  }\n\n  Carousel.prototype.keydown = function (e) {\n    if (/input|textarea/i.test(e.target.tagName)) return\n    switch (e.which) {\n      case 37: this.prev(); break\n      case 39: this.next(); break\n      default: return\n    }\n\n    e.preventDefault()\n  }\n\n  Carousel.prototype.cycle = function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getItemIndex = function (item) {\n    this.$items = item.parent().children('.item')\n    return this.$items.index(item || this.$active)\n  }\n\n  Carousel.prototype.getItemForDirection = function (direction, active) {\n    var activeIndex = this.getItemIndex(active)\n    var willWrap = (direction == 'prev' && activeIndex === 0)\n                || (direction == 'next' && activeIndex == (this.$items.length - 1))\n    if (willWrap && !this.options.wrap) return active\n    var delta = direction == 'prev' ? -1 : 1\n    var itemIndex = (activeIndex + delta) % this.$items.length\n    return this.$items.eq(itemIndex)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || this.getItemForDirection(type, $active)\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var that      = this\n\n    if ($next.hasClass('active')) return (this.sliding = false)\n\n    var relatedTarget = $next[0]\n    var slideEvent = $.Event('slide.bs.carousel', {\n      relatedTarget: relatedTarget,\n      direction: direction\n    })\n    this.$element.trigger(slideEvent)\n    if (slideEvent.isDefaultPrevented()) return\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n      $nextIndicator && $nextIndicator.addClass('active')\n    }\n\n    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      $next.addClass(type)\n      $next[0].offsetWidth // force reflow\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one('bsTransitionEnd', function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger(slidEvent)\n          }, 0)\n        })\n        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n    } else {\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger(slidEvent)\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  var old = $.fn.carousel\n\n  $.fn.carousel             = Plugin\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  var clickHandler = function (e) {\n    var href\n    var $this   = $(this)\n    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n    if (!$target.hasClass('carousel')) return\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    Plugin.call($target, options)\n\n    if (slideIndex) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  }\n\n  $(document)\n    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      Plugin.call($carousel, $carousel.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: collapse.js v3.3.2\n * http://getbootstrap.com/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.$trigger      = $(this.options.trigger).filter('[href=\"#' + element.id + '\"], [data-target=\"#' + element.id + '\"]')\n    this.transitioning = null\n\n    if (this.options.parent) {\n      this.$parent = this.getParent()\n    } else {\n      this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n    }\n\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.VERSION  = '3.3.2'\n\n  Collapse.TRANSITION_DURATION = 350\n\n  Collapse.DEFAULTS = {\n    toggle: true,\n    trigger: '[data-toggle=\"collapse\"]'\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var activesData\n    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n    if (actives && actives.length) {\n      activesData = actives.data('bs.collapse')\n      if (activesData && activesData.transitioning) return\n    }\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    if (actives && actives.length) {\n      Plugin.call(actives, 'hide')\n      activesData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')[dimension](0)\n      .attr('aria-expanded', true)\n\n    this.$trigger\n      .removeClass('collapsed')\n      .attr('aria-expanded', true)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse in')[dimension]('')\n      this.transitioning = 0\n      this.$element\n        .trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse in')\n      .attr('aria-expanded', false)\n\n    this.$trigger\n      .addClass('collapsed')\n      .attr('aria-expanded', false)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse')\n        .trigger('hidden.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n  Collapse.prototype.getParent = function () {\n    return $(this.options.parent)\n      .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n      .each($.proxy(function (i, element) {\n        var $element = $(element)\n        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n      }, this))\n      .end()\n  }\n\n  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n    var isOpen = $element.hasClass('in')\n\n    $element.attr('aria-expanded', isOpen)\n    $trigger\n      .toggleClass('collapsed', !isOpen)\n      .attr('aria-expanded', isOpen)\n  }\n\n  function getTargetFromTrigger($trigger) {\n    var href\n    var target = $trigger.attr('data-target')\n      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n    return $(target)\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data && options.toggle && option == 'show') options.toggle = false\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.collapse\n\n  $.fn.collapse             = Plugin\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n    var $this   = $(this)\n\n    if (!$this.attr('data-target')) e.preventDefault()\n\n    var $target = getTargetFromTrigger($this)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })\n\n    Plugin.call($target, option)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: dropdown.js v3.3.2\n * http://getbootstrap.com/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=\"dropdown\"]'\n  var Dropdown = function (element) {\n    $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.VERSION = '3.3.2'\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we use a backdrop because click events don't delegate\n        $('<div class=\"dropdown-backdrop\"/>').insertAfter($(this)).on('click', clearMenus)\n      }\n\n      var relatedTarget = { relatedTarget: this }\n      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this\n        .trigger('focus')\n        .attr('aria-expanded', 'true')\n\n      $parent\n        .toggleClass('open')\n        .trigger('shown.bs.dropdown', relatedTarget)\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {\n      if (e.which == 27) $parent.find(toggle).trigger('focus')\n      return $this.trigger('click')\n    }\n\n    var desc = ' li:not(.divider):visible a'\n    var $items = $parent.find('[role=\"menu\"]' + desc + ', [role=\"listbox\"]' + desc)\n\n    if (!$items.length) return\n\n    var index = $items.index(e.target)\n\n    if (e.which == 38 && index > 0)                 index--                        // up\n    if (e.which == 40 && index < $items.length - 1) index++                        // down\n    if (!~index)                                      index = 0\n\n    $items.eq(index).trigger('focus')\n  }\n\n  function clearMenus(e) {\n    if (e && e.which === 3) return\n    $(backdrop).remove()\n    $(toggle).each(function () {\n      var $this         = $(this)\n      var $parent       = getParent($this)\n      var relatedTarget = { relatedTarget: this }\n\n      if (!$parent.hasClass('open')) return\n\n      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this.attr('aria-expanded', 'false')\n      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)\n    })\n  }\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = selector && $(selector)\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.dropdown')\n\n      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown             = Plugin\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"menu\"]', Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"listbox\"]', Dropdown.prototype.keydown)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: modal.js v3.3.2\n * http://getbootstrap.com/javascript/#modals\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options        = options\n    this.$body          = $(document.body)\n    this.$element       = $(element)\n    this.$backdrop      =\n    this.isShown        = null\n    this.scrollbarWidth = 0\n\n    if (this.options.remote) {\n      this.$element\n        .find('.modal-content')\n        .load(this.options.remote, $.proxy(function () {\n          this.$element.trigger('loaded.bs.modal')\n        }, this))\n    }\n  }\n\n  Modal.VERSION  = '3.3.2'\n\n  Modal.TRANSITION_DURATION = 300\n  Modal.BACKDROP_TRANSITION_DURATION = 150\n\n  Modal.DEFAULTS = {\n    backdrop: true,\n    keyboard: true,\n    show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this.isShown ? this.hide() : this.show(_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.checkScrollbar()\n    this.setScrollbar()\n    this.$body.addClass('modal-open')\n\n    this.escape()\n    this.resize()\n\n    this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(that.$body) // don't move modals dom position\n      }\n\n      that.$element\n        .show()\n        .scrollTop(0)\n\n      if (that.options.backdrop) that.adjustBackdrop()\n      that.adjustDialog()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element\n        .addClass('in')\n        .attr('aria-hidden', false)\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$element.find('.modal-dialog') // wait for modal to slide in\n          .one('bsTransitionEnd', function () {\n            that.$element.trigger('focus').trigger(e)\n          })\n          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n        that.$element.trigger('focus').trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n    this.resize()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .attr('aria-hidden', true)\n      .off('click.dismiss.bs.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {\n          this.$element.trigger('focus')\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keydown.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.resize = function () {\n    if (this.isShown) {\n      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n    } else {\n      $(window).off('resize.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.$body.removeClass('modal-open')\n      that.resetAdjustments()\n      that.resetScrollbar()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $('<div class=\"modal-backdrop ' + animate + '\" />')\n        .prependTo(this.$element)\n        .on('click.dismiss.bs.modal', $.proxy(function (e) {\n          if (e.target !== e.currentTarget) return\n          this.options.backdrop == 'static'\n            ? this.$element[0].focus.call(this.$element[0])\n            : this.hide.call(this)\n        }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one('bsTransitionEnd', callback)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      var callbackRemove = function () {\n        that.removeBackdrop()\n        callback && callback()\n      }\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.$backdrop\n          .one('bsTransitionEnd', callbackRemove)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callbackRemove()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n  // these following methods are used to handle overflowing modals\n\n  Modal.prototype.handleUpdate = function () {\n    if (this.options.backdrop) this.adjustBackdrop()\n    this.adjustDialog()\n  }\n\n  Modal.prototype.adjustBackdrop = function () {\n    this.$backdrop\n      .css('height', 0)\n      .css('height', this.$element[0].scrollHeight)\n  }\n\n  Modal.prototype.adjustDialog = function () {\n    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n    this.$element.css({\n      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n    })\n  }\n\n  Modal.prototype.resetAdjustments = function () {\n    this.$element.css({\n      paddingLeft: '',\n      paddingRight: ''\n    })\n  }\n\n  Modal.prototype.checkScrollbar = function () {\n    this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight\n    this.scrollbarWidth = this.measureScrollbar()\n  }\n\n  Modal.prototype.setScrollbar = function () {\n    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)\n  }\n\n  Modal.prototype.resetScrollbar = function () {\n    this.$body.css('padding-right', '')\n  }\n\n  Modal.prototype.measureScrollbar = function () { // thx walsh\n    var scrollDiv = document.createElement('div')\n    scrollDiv.className = 'modal-scrollbar-measure'\n    this.$body.append(scrollDiv)\n    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n    this.$body[0].removeChild(scrollDiv)\n    return scrollbarWidth\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option, _relatedTarget) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  var old = $.fn.modal\n\n  $.fn.modal             = Plugin\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) // strip for ie7\n    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    if ($this.is('a')) e.preventDefault()\n\n    $target.one('show.bs.modal', function (showEvent) {\n      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n      $target.one('hidden.bs.modal', function () {\n        $this.is(':visible') && $this.trigger('focus')\n      })\n    })\n    Plugin.call($target, option, this)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tooltip.js v3.3.2\n * http://getbootstrap.com/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       =\n    this.options    =\n    this.enabled    =\n    this.timeout    =\n    this.hoverState =\n    this.$element   = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.VERSION  = '3.3.2'\n\n  Tooltip.TRANSITION_DURATION = 150\n\n  Tooltip.DEFAULTS = {\n    animation: true,\n    placement: 'top',\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',\n    trigger: 'hover focus',\n    title: '',\n    delay: 0,\n    html: false,\n    container: false,\n    viewport: {\n      selector: 'body',\n      padding: 0\n    }\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled   = true\n    this.type      = type\n    this.$element  = $(element)\n    this.options   = this.getOptions(options)\n    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    options = $.extend({}, this.getDefaults(), this.$element.data(), options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay,\n        hide: options.delay\n      }\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (self && self.$tip && self.$tip.is(':visible')) {\n      self.hoverState = 'in'\n      return\n    }\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.' + this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n      if (e.isDefaultPrevented() || !inDom) return\n      var that = this\n\n      var $tip = this.tip()\n\n      var tipId = this.getUID(this.type)\n\n      this.setContent()\n      $tip.attr('id', tipId)\n      this.$element.attr('aria-describedby', tipId)\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n        .data('bs.' + this.type, this)\n\n      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var orgPlacement = placement\n        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()\n        var containerDim = this.getPosition($container)\n\n        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :\n                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :\n                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :\n                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n\n      var complete = function () {\n        var prevHoverState = that.hoverState\n        that.$element.trigger('shown.bs.' + that.type)\n        that.hoverState = null\n\n        if (prevHoverState == 'out') that.leave(that)\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        $tip\n          .one('bsTransitionEnd', complete)\n          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n        complete()\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function (offset, placement) {\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  = offset.top  + marginTop\n    offset.left = offset.left + marginLeft\n\n    // $.fn.offset doesn't round pixel values\n    // so we use setOffset directly with our own function B-0\n    $.offset.setOffset($tip[0], $.extend({\n      using: function (props) {\n        $tip.css({\n          top: Math.round(props.top),\n          left: Math.round(props.left)\n        })\n      }\n    }, offset), 0)\n\n    $tip.addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      offset.top = offset.top + height - actualHeight\n    }\n\n    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n    if (delta.left) offset.left += delta.left\n    else offset.top += delta.top\n\n    var isVertical          = /top|bottom/.test(placement)\n    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n    $tip.offset(offset)\n    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n  }\n\n  Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {\n    this.arrow()\n      .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n      .css(isHorizontal ? 'top' : 'left', '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function (callback) {\n    var that = this\n    var $tip = this.tip()\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n      that.$element\n        .removeAttr('aria-describedby')\n        .trigger('hidden.bs.' + that.type)\n      callback && callback()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && this.$tip.hasClass('fade') ?\n      $tip\n        .one('bsTransitionEnd', complete)\n        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n      complete()\n\n    this.hoverState = null\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function ($element) {\n    $element   = $element || this.$element\n\n    var el     = $element[0]\n    var isBody = el.tagName == 'BODY'\n\n    var elRect    = el.getBoundingClientRect()\n    if (elRect.width == null) {\n      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n    }\n    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()\n    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n    return $.extend({}, elRect, scroll, outerDims, elOffset)\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n  }\n\n  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n    var delta = { top: 0, left: 0 }\n    if (!this.$viewport) return delta\n\n    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n    var viewportDimensions = this.getPosition(this.$viewport)\n\n    if (/right|left/.test(placement)) {\n      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll\n      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n      if (topEdgeOffset < viewportDimensions.top) { // top overflow\n        delta.top = viewportDimensions.top - topEdgeOffset\n      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n      }\n    } else {\n      var leftEdgeOffset  = pos.left - viewportPadding\n      var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n      if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n        delta.left = viewportDimensions.left - leftEdgeOffset\n      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow\n        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n      }\n    }\n\n    return delta\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.getUID = function (prefix) {\n    do prefix += ~~(Math.random() * 1000000)\n    while (document.getElementById(prefix))\n    return prefix\n  }\n\n  Tooltip.prototype.tip = function () {\n    return (this.$tip = this.$tip || $(this.options.template))\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = this\n    if (e) {\n      self = $(e.currentTarget).data('bs.' + this.type)\n      if (!self) {\n        self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n        $(e.currentTarget).data('bs.' + this.type, self)\n      }\n    }\n\n    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n  }\n\n  Tooltip.prototype.destroy = function () {\n    var that = this\n    clearTimeout(this.timeout)\n    this.hide(function () {\n      that.$element.off('.' + that.type).removeData('bs.' + that.type)\n    })\n  }\n\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data && option == 'destroy') return\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip             = Plugin\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: popover.js v3.3.2\n * http://getbootstrap.com/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.VERSION  = '3.3.2'\n\n  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right',\n    trigger: 'click',\n    content: '',\n    template: '<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events\n      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'\n    ](content)\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n            o.content.call($e[0]) :\n            o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n  }\n\n  Popover.prototype.tip = function () {\n    if (!this.$tip) this.$tip = $(this.options.template)\n    return this.$tip\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data && option == 'destroy') return\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.popover\n\n  $.fn.popover             = Plugin\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: scrollspy.js v3.3.2\n * http://getbootstrap.com/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    var process  = $.proxy(this.process, this)\n\n    this.$body          = $('body')\n    this.$scrollElement = $(element).is('body') ? $(window) : $(element)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target || '') + ' .nav li > a'\n    this.offsets        = []\n    this.targets        = []\n    this.activeTarget   = null\n    this.scrollHeight   = 0\n\n    this.$scrollElement.on('scroll.bs.scrollspy', process)\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.VERSION  = '3.3.2'\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.getScrollHeight = function () {\n    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var offsetMethod = 'offset'\n    var offsetBase   = 0\n\n    if (!$.isWindow(this.$scrollElement[0])) {\n      offsetMethod = 'position'\n      offsetBase   = this.$scrollElement.scrollTop()\n    }\n\n    this.offsets = []\n    this.targets = []\n    this.scrollHeight = this.getScrollHeight()\n\n    var self     = this\n\n    this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#./.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && $href.is(':visible')\n          && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        self.offsets.push(this[0])\n        self.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.getScrollHeight()\n    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (this.scrollHeight != scrollHeight) {\n      this.refresh()\n    }\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n    }\n\n    if (activeTarget && scrollTop < offsets[0]) {\n      this.activeTarget = null\n      return this.clear()\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])\n        && this.activate(targets[i])\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    this.clear()\n\n    var selector = this.selector +\n        '[data-target=\"' + target + '\"],' +\n        this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length) {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate.bs.scrollspy')\n  }\n\n  ScrollSpy.prototype.clear = function () {\n    $(this.selector)\n      .parentsUntil(this.options.target, '.active')\n      .removeClass('active')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy             = Plugin\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load.bs.scrollspy.data-api', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      Plugin.call($spy, $spy.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tab.js v3.3.2\n * http://getbootstrap.com/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    this.element = $(element)\n  }\n\n  Tab.VERSION = '3.3.2'\n\n  Tab.TRANSITION_DURATION = 150\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.data('target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var $previous = $ul.find('.active:last a')\n    var hideEvent = $.Event('hide.bs.tab', {\n      relatedTarget: $this[0]\n    })\n    var showEvent = $.Event('show.bs.tab', {\n      relatedTarget: $previous[0]\n    })\n\n    $previous.trigger(hideEvent)\n    $this.trigger(showEvent)\n\n    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n    var $target = $(selector)\n\n    this.activate($this.closest('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $previous.trigger({\n        type: 'hidden.bs.tab',\n        relatedTarget: $this[0]\n      })\n      $this.trigger({\n        type: 'shown.bs.tab',\n        relatedTarget: $previous[0]\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n          .removeClass('active')\n        .end()\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', false)\n\n      element\n        .addClass('active')\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', true)\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu')) {\n        element\n          .closest('li.dropdown')\n            .addClass('active')\n          .end()\n          .find('[data-toggle=\"tab\"]')\n            .attr('aria-expanded', true)\n      }\n\n      callback && callback()\n    }\n\n    $active.length && transition ?\n      $active\n        .one('bsTransitionEnd', next)\n        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tab\n\n  $.fn.tab             = Plugin\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  var clickHandler = function (e) {\n    e.preventDefault()\n    Plugin.call($(this), 'show')\n  }\n\n  $(document)\n    .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n    .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: affix.js v3.3.2\n * http://getbootstrap.com/javascript/#affix\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n\n    this.$target = $(this.options.target)\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element     = $(element)\n    this.affixed      =\n    this.unpin        =\n    this.pinnedOffset = null\n\n    this.checkPosition()\n  }\n\n  Affix.VERSION  = '3.3.2'\n\n  Affix.RESET    = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0,\n    target: window\n  }\n\n  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n    var scrollTop    = this.$target.scrollTop()\n    var position     = this.$element.offset()\n    var targetHeight = this.$target.height()\n\n    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n    if (this.affixed == 'bottom') {\n      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n    }\n\n    var initializing   = this.affixed == null\n    var colliderTop    = initializing ? scrollTop : position.top\n    var colliderHeight = initializing ? targetHeight : height\n\n    if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n    return false\n  }\n\n  Affix.prototype.getPinnedOffset = function () {\n    if (this.pinnedOffset) return this.pinnedOffset\n    this.$element.removeClass(Affix.RESET).addClass('affix')\n    var scrollTop = this.$target.scrollTop()\n    var position  = this.$element.offset()\n    return (this.pinnedOffset = position.top - scrollTop)\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var height       = this.$element.height()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n    var scrollHeight = $('body').height()\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n    if (this.affixed != affix) {\n      if (this.unpin != null) this.$element.css('top', '')\n\n      var affixType = 'affix' + (affix ? '-' + affix : '')\n      var e         = $.Event(affixType + '.bs.affix')\n\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      this.affixed = affix\n      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n      this.$element\n        .removeClass(Affix.RESET)\n        .addClass(affixType)\n        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n    }\n\n    if (affix == 'bottom') {\n      this.$element.offset({\n        top: scrollHeight - height - offsetBottom\n      })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.affix\n\n  $.fn.affix             = Plugin\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop    != null) data.offset.top    = data.offsetTop\n\n      Plugin.call($spy, data)\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "js/hux-blog.js",
    "content": "/*!\n * Clean Blog v1.0.0 (http://startbootstrap.com)\n * Copyright 2015 Start Bootstrap\n * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE)\n */\n\n /*!\n * Hux Blog v1.6.0 (http://startbootstrap.com)\n * Copyright 2016 @huxpro\n * Licensed under Apache 2.0 \n */\n\n// Tooltip Init\n// Unuse by Hux since V1.6: Titles now display by default so there is no need for tooltip\n// $(function() {\n//     $(\"[data-toggle='tooltip']\").tooltip();\n// });\n\n\n// make all images responsive\n/* \n * Unuse by Hux\n * actually only Portfolio-Pages can't use it and only post-img need it.\n * so I modify the _layout/post and CSS to make post-img responsive!\n */\n// $(function() {\n//  $(\"img\").addClass(\"img-responsive\");\n// });\n\n// responsive tables\n$(document).ready(function() {\n    $(\"table\").wrap(\"<div class='table-responsive'></div>\");\n    $(\"table\").addClass(\"table\");\n});\n\n// responsive embed videos\n$(document).ready(function() {\n    $('iframe[src*=\"youtube.com\"]').wrap('<div class=\"embed-responsive embed-responsive-16by9\"></div>');\n    $('iframe[src*=\"youtube.com\"]').addClass('embed-responsive-item');\n    $('iframe[src*=\"vimeo.com\"]').wrap('<div class=\"embed-responsive embed-responsive-16by9\"></div>');\n    $('iframe[src*=\"vimeo.com\"]').addClass('embed-responsive-item');\n});\n\n// Navigation Scripts to Show Header on Scroll-Up\njQuery(document).ready(function($) {\n    var MQL = 1170;\n\n    //primary navigation slide-in effect\n    if ($(window).width() > MQL) {\n        var headerHeight = $('.navbar-custom').height(),\n            bannerHeight  = $('.intro-header .container').height();     \n        $(window).on('scroll', {\n                previousTop: 0\n            },\n            function() {\n                var currentTop = $(window).scrollTop(),\n                    $catalog = $('.side-catalog');\n\n                //check if user is scrolling up by mouse or keyborad\n                if (currentTop < this.previousTop) {\n                    //if scrolling up...\n                    if (currentTop > 0 && $('.navbar-custom').hasClass('is-fixed')) {\n                        $('.navbar-custom').addClass('is-visible');\n                    } else {\n                        $('.navbar-custom').removeClass('is-visible is-fixed');\n                    }\n                } else {\n                    //if scrolling down...\n                    $('.navbar-custom').removeClass('is-visible');\n                    if (currentTop > headerHeight && !$('.navbar-custom').hasClass('is-fixed')) $('.navbar-custom').addClass('is-fixed');\n                }\n                this.previousTop = currentTop;\n\n\n                //adjust the appearance of side-catalog\n                $catalog.show()\n                if (currentTop > (bannerHeight + 41)) {\n                    $catalog.addClass('fixed')\n                } else {\n                    $catalog.removeClass('fixed')\n                }\n            });\n    }\n});"
  },
  {
    "path": "js/jquery.js",
    "content": "/*!\n * jQuery JavaScript Library v2.1.3\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-12-18T15:11Z\n */\n\n(function( global, factory ) {\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\t\t// For CommonJS and CommonJS-like environments where a proper `window`\n\t\t// is present, execute the factory and get jQuery.\n\t\t// For environments that do not have a `window` with a `document`\n\t\t// (such as Node.js), expose a factory as module.exports.\n\t\t// This accentuates the need for the creation of a real `window`.\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket #14549 for more info.\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n}(typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Support: Firefox 18+\n// Can't be in strict mode, several libs including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n//\n\nvar arr = [];\n\nvar slice = arr.slice;\n\nvar concat = arr.concat;\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar support = {};\n\n\n\nvar\n\t// Use the correct document accordingly with window argument (sandbox)\n\tdocument = window.document,\n\n\tversion = \"2.1.3\",\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init( selector, context );\n\t},\n\n\t// Support: Android<4.1\n\t// Make sure we trim BOM and NBSP\n\trtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn letter.toUpperCase();\n\t};\n\njQuery.fn = jQuery.prototype = {\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num != null ?\n\n\t\t\t// Return just the one element from the set\n\t\t\t( num < 0 ? this[ num + this.length ] : this[ num ] ) :\n\n\t\t\t// Return all the elements in a clean array\n\t\t\tslice.call( this );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\t\tret.context = this.context;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\n\t\t// Skip the boolean and the target\n\t\ttarget = arguments[ i ] || {};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// Extend jQuery itself if only one argument is passed\n\tif ( i === length ) {\n\t\ttarget = this;\n\t\ti--;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\t// Unique for each copy of jQuery on the page\n\texpando: \"jQuery\" + ( version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\tnoop: function() {},\n\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray,\n\n\tisWindow: function( obj ) {\n\t\treturn obj != null && obj === obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\t// parseFloat NaNs numeric-cast false positives (null|true|false|\"\")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n\t\t// subtraction forces infinities to NaN\n\t\t// adding 1 corrects loss of precision from parseFloat (#15100)\n\t\treturn !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\t// Not plain objects:\n\t\t// - Any object or value whose internal [[Class]] property is not \"[object Object]\"\n\t\t// - DOM nodes\n\t\t// - window\n\t\tif ( jQuery.type( obj ) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( obj.constructor &&\n\t\t\t\t!hasOwn.call( obj.constructor.prototype, \"isPrototypeOf\" ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// If the function hasn't returned already, we're confident that\n\t\t// |obj| is a plain object, created by {} or constructed with new Object\n\t\treturn true;\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\ttype: function( obj ) {\n\t\tif ( obj == null ) {\n\t\t\treturn obj + \"\";\n\t\t}\n\t\t// Support: Android<4.0, iOS<6 (functionish RegExp)\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\t\tclass2type[ toString.call(obj) ] || \"object\" :\n\t\t\ttypeof obj;\n\t},\n\n\t// Evaluates a script in a global context\n\tglobalEval: function( code ) {\n\t\tvar script,\n\t\t\tindirect = eval;\n\n\t\tcode = jQuery.trim( code );\n\n\t\tif ( code ) {\n\t\t\t// If the code includes a valid, prologue position\n\t\t\t// strict mode pragma, execute code by injecting a\n\t\t\t// script tag into the document.\n\t\t\tif ( code.indexOf(\"use strict\") === 1 ) {\n\t\t\t\tscript = document.createElement(\"script\");\n\t\t\t\tscript.text = code;\n\t\t\t\tdocument.head.appendChild( script ).parentNode.removeChild( script );\n\t\t\t} else {\n\t\t\t// Otherwise, avoid the DOM node creation, insertion\n\t\t\t// and removal by using an indirect global eval\n\t\t\t\tindirect( code );\n\t\t\t}\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Support: IE9-11+\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisArray = isArraylike( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// Support: Android<4.1\n\ttrim: function( text ) {\n\t\treturn text == null ?\n\t\t\t\"\" :\n\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArraylike( Object(arr) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpush.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\treturn arr == null ? -1 : indexOf.call( arr, elem, i );\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar len = +second.length,\n\t\t\tj = 0,\n\t\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, invert ) {\n\t\tvar callbackInverse,\n\t\t\tmatches = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tcallbackInverse = !callback( elems[ i ], i );\n\t\t\tif ( callbackInverse !== callbackExpect ) {\n\t\t\t\tmatches.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tisArray = isArraylike( elems ),\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar tmp, args, proxy;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context || this, args.concat( slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\tnow: Date.now,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n});\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\nfunction isArraylike( obj ) {\n\tvar length = obj.length,\n\t\ttype = jQuery.type( obj );\n\n\tif ( type === \"function\" || jQuery.isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\tif ( obj.nodeType === 1 && length ) {\n\t\treturn true;\n\t}\n\n\treturn type === \"array\" || length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n}\nvar Sizzle =\n/*!\n * Sizzle CSS Selector Engine v2.2.0-pre\n * http://sizzlejs.com/\n *\n * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-12-16\n */\n(function( window ) {\n\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\ttokenize,\n\tcompile,\n\tselect,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + 1 * new Date(),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// General-purpose constants\n\tMAX_NEGATIVE = 1 << 31,\n\n\t// Instance methods\n\thasOwn = ({}).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpush_native = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\t// Use a stripped-down indexOf as it's faster than native\n\t// http://jsperf.com/thor-indexof-vs-for/5\n\tindexOf = function( list, elem ) {\n\t\tvar i = 0,\n\t\t\tlen = list.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( list[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")(?:\" + whitespace +\n\t\t// Operator (capture 2)\n\t\t\"*([*^$|!~]?=)\" + whitespace +\n\t\t// \"Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]\"\n\t\t\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\" + identifier + \"))|)\" + whitespace +\n\t\t\"*\\\\]\",\n\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\((\" +\n\t\t// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:\n\t\t// 1. quoted (capture 3; capture 4 or capture 5)\n\t\t\"('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|\" +\n\t\t// 2. simple (capture 6)\n\t\t\"((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes + \")*)|\" +\n\t\t// 3. anything else (capture 2)\n\t\t\".*\" +\n\t\t\")\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trwhitespace = new RegExp( whitespace + \"+\", \"g\" ),\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\n\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*?)\" + whitespace + \"*\\\\]\", \"g\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\trescape = /'|\\\\/g,\n\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\n\tfunescape = function( _, escaped, escapedWhitespace ) {\n\t\tvar high = \"0x\" + escaped - 0x10000;\n\t\t// NaN means non-codepoint\n\t\t// Support: Firefox<24\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\n\t\treturn high !== high || escapedWhitespace ?\n\t\t\tescaped :\n\t\t\thigh < 0 ?\n\t\t\t\t// BMP codepoint\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t},\n\n\t// Used for iframes\n\t// See setDocument()\n\t// Removing the function wrapper causes a \"Permission Denied\"\n\t// error in IE\n\tunloadHandler = function() {\n\t\tsetDocument();\n\t};\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t(arr = slice.call( preferredDoc.childNodes )),\n\t\tpreferredDoc.childNodes\n\t);\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpush_native.apply( target, slice.call(els) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar match, elem, m, nodeType,\n\t\t// QSA vars\n\t\ti, groups, old, nid, newContext, newSelector;\n\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\n\tcontext = context || document;\n\tresults = results || [];\n\tnodeType = context.nodeType;\n\n\tif ( typeof selector !== \"string\" || !selector ||\n\t\tnodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {\n\n\t\treturn results;\n\t}\n\n\tif ( !seed && documentIsHTML ) {\n\n\t\t// Try to shortcut find operations when possible (e.g., not under DocumentFragment)\n\t\tif ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document (jQuery #6963)\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\t// QSA path\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\n\t\t\tnid = old = expando;\n\t\t\tnewContext = context;\n\t\t\tnewSelector = nodeType !== 1 && selector;\n\n\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t// IE 8 doesn't work on object elements\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t}\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\ti = groups.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\n\t\t\t\t}\n\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;\n\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t}\n\n\t\t\tif ( newSelector ) {\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError) {\n\t\t\t\t} finally {\n\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn (cache[ key + \" \" ] = value);\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created div and expects a boolean result\n */\nfunction assert( fn ) {\n\tvar div = document.createElement(\"div\");\n\n\ttry {\n\t\treturn !!fn( div );\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\t// Remove from its parent by default\n\t\tif ( div.parentNode ) {\n\t\t\tdiv.parentNode.removeChild( div );\n\t\t}\n\t\t// release memory in IE\n\t\tdiv = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split(\"|\"),\n\t\ti = attrs.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[i] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( (cur = cur.nextSibling) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== \"undefined\" && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare, parent,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc;\n\n\t// If no document and documentElement is available, return\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Set our document\n\tdocument = doc;\n\tdocElem = doc.documentElement;\n\tparent = doc.defaultView;\n\n\t// Support: IE>8\n\t// If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\n\t// IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\n\t// IE6-8 do not support the defaultView property so parent will be undefined\n\tif ( parent && parent !== parent.top ) {\n\t\t// IE11 does not have attachEvent, so all must suffer\n\t\tif ( parent.addEventListener ) {\n\t\t\tparent.addEventListener( \"unload\", unloadHandler, false );\n\t\t} else if ( parent.attachEvent ) {\n\t\t\tparent.attachEvent( \"onunload\", unloadHandler );\n\t\t}\n\t}\n\n\t/* Support tests\n\t---------------------------------------------------------------------- */\n\tdocumentIsHTML = !isXML( doc );\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties\n\t// (excepting IE8 booleans)\n\tsupport.attributes = assert(function( div ) {\n\t\tdiv.className = \"i\";\n\t\treturn !div.getAttribute(\"className\");\n\t});\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert(function( div ) {\n\t\tdiv.appendChild( doc.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t});\n\n\t// Support: IE<9\n\tsupport.getElementsByClassName = rnative.test( doc.getElementsByClassName );\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert(function( div ) {\n\t\tdocElem.appendChild( div ).id = expando;\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\n\t});\n\n\t// ID find and filter\n\tif ( support.getById ) {\n\t\tExpr.find[\"ID\"] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\treturn m && m.parentNode ? [ m ] : [];\n\t\t\t}\n\t\t};\n\t\tExpr.filter[\"ID\"] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\n\t\t\t};\n\t\t};\n\t} else {\n\t\t// Support: IE6/7\n\t\t// getElementById is not reliable as a find shortcut\n\t\tdelete Expr.find[\"ID\"];\n\n\t\tExpr.filter[\"ID\"] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== \"undefined\" && elem.getAttributeNode(\"id\");\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\n\t\t\t// DocumentFragment nodes don't have gEBTN\n\t\t\t} else if ( support.qsa ) {\n\t\t\t\treturn context.querySelectorAll( tag );\n\t\t\t}\n\t\t} :\n\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\t\t\t\t// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( (elem = results[i++]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See http://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdocElem.appendChild( div ).innerHTML = \"<a id='\" + expando + \"'></a>\" +\n\t\t\t\t\"<select id='\" + expando + \"-\\f]' msallowcapture=''>\" +\n\t\t\t\t\"<option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 11-12.16\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\t// The test attribute must be unknown in Opera but \"safe\" for WinRT\n\t\t\t// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section\n\t\t\tif ( div.querySelectorAll(\"[msallowcapture^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+\n\t\t\tif ( !div.querySelectorAll( \"[id~=\" + expando + \"-]\" ).length ) {\n\t\t\t\trbuggyQSA.push(\"~=\");\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\n\t\t\t// Support: Safari 8+, iOS 8+\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t\t// In-page `selector#id sibing-combinator selector` fails\n\t\t\tif ( !div.querySelectorAll( \"a#\" + expando + \"+*\" ).length ) {\n\t\t\t\trbuggyQSA.push(\".#.+[+~]\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = doc.createElement(\"input\");\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tdiv.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( div.querySelectorAll(\"[name=d]\").length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tdiv.querySelectorAll(\"*,:x\");\n\t\t\trbuggyQSA.push(\",.*:\");\n\t\t});\n\t}\n\n\tif ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||\n\t\tdocElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector) )) ) {\n\n\t\tassert(function( div ) {\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( div, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t});\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully does not implement inclusive descendent\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t));\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( (b = b.parentNode) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\tcompare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\tif ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\t\t\treturn a === doc ? -1 :\n\t\t\t\tb === doc ? 1 :\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[i] === bp[i] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\tap[i] === preferredDoc ? -1 :\n\t\t\tbp[i] === preferredDoc ? 1 :\n\t\t\t0;\n\t};\n\n\treturn doc;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\t// Make sure that attribute selectors are quoted\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch (e) {}\n\t}\n\n\treturn Sizzle( expr, document, null, [ elem ] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\t// Set document vars if needed\n\tif ( ( context.ownerDocument || context ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( (elem = results[i++]) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( (node = elem[i++]) ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[3] || match[4] || match[5] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[3] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[3] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[6] && match[2];\n\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[3] ) {\n\t\t\t\tmatch[2] = match[4] || match[5] || \"\";\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() { return true; } :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== \"undefined\" && elem.getAttribute(\"class\") || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result.replace( rwhitespace, \" \" ) + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\n\t\t\t\t\t\t\tdiff = cache[1];\n\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\t// Don't keep the element (issue #299)\n\t\t\t\t\tinput[0] = null;\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\ttext = text.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE<8\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\ntokenize = Sizzle.tokenize = function( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( (tokens = []) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\n\t\t\t});\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n};\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[i].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\n\t\t\t\t\t\tif ( (oldCache = outerCache[ dir ]) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn (newCache[ 2 ] = oldCache[ 2 ]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\touterCache[ dir ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\tvar ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t\t// Avoid hanging onto element (issue #299)\n\t\t\tcheckContext = null;\n\t\t\treturn ret;\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", outermost ),\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !match ) {\n\t\t\tmatch = tokenize( selector );\n\t\t}\n\t\ti = match.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( match[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\n\t\t// Save selector and tokenization\n\t\tcached.selector = selector;\n\t}\n\treturn cached;\n};\n\n/**\n * A low-level selection function that works with Sizzle's compiled\n *  selector functions\n * @param {String|Function} selector A selector or a pre-compiled\n *  selector function built with Sizzle.compile\n * @param {Element} context\n * @param {Array} [results]\n * @param {Array} [seed] A set of elements to match against\n */\nselect = Sizzle.select = function( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tcompiled = typeof selector === \"function\" && selector,\n\t\tmatch = !seed && tokenize( (selector = compiled.selector || selector) );\n\n\tresults = results || [];\n\n\t// Try to minimize operations if there is no seed and only one group\n\tif ( match.length === 1 ) {\n\n\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\ttokens = match[0] = match[0].slice( 0 );\n\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\n\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\n\t\t\tif ( !context ) {\n\t\t\t\treturn results;\n\n\t\t\t// Precompiled matchers will still verify ancestry, so step up a level\n\t\t\t} else if ( compiled ) {\n\t\t\t\tcontext = context.parentNode;\n\t\t\t}\n\n\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t}\n\n\t\t// Fetch a seed set for right-to-left matching\n\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\n\t\twhile ( i-- ) {\n\t\t\ttoken = tokens[i];\n\n\t\t\t// Abort if we hit a combinator\n\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\tif ( (seed = find(\n\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\n\t\t\t\t\trsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context\n\t\t\t\t)) ) {\n\n\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function if one is not provided\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\t( compiled || compile( selector, match ) )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\trsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n};\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\n\n// Support: Chrome 14-35+\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert(function( div1 ) {\n\t// Should return 1, but returns 4 (following)\n\treturn div1.compareDocumentPosition( document.createElement(\"div\") ) & 1;\n});\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert(function( div ) {\n\tdiv.innerHTML = \"<a href='#'></a>\";\n\treturn div.firstChild.getAttribute(\"href\") === \"#\" ;\n}) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert(function( div ) {\n\tdiv.innerHTML = \"<input/>\";\n\tdiv.firstChild.setAttribute( \"value\", \"\" );\n\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\n}) ) {\n\taddHandle( \"value\", function( elem, name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert(function( div ) {\n\treturn div.getAttribute(\"disabled\") == null;\n}) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t\t(val = elem.getAttributeNode( name )) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\tnull;\n\t\t}\n\t});\n}\n\nreturn Sizzle;\n\n})( window );\n\n\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n\nvar rneedsContext = jQuery.expr.match.needsContext;\n\nvar rsingleTag = (/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/);\n\n\n\nvar risSimple = /^.[^:#\\[\\.,]*$/;\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\t/* jshint -W018 */\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t});\n\n\t}\n\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t});\n\n\t}\n\n\tif ( typeof qualifier === \"string\" ) {\n\t\tif ( risSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter( qualifier, elements, not );\n\t\t}\n\n\t\tqualifier = jQuery.filter( qualifier, elements );\n\t}\n\n\treturn jQuery.grep( elements, function( elem ) {\n\t\treturn ( indexOf.call( qualifier, elem ) >= 0 ) !== not;\n\t});\n}\n\njQuery.filter = function( expr, elems, not ) {\n\tvar elem = elems[ 0 ];\n\n\tif ( not ) {\n\t\texpr = \":not(\" + expr + \")\";\n\t}\n\n\treturn elems.length === 1 && elem.nodeType === 1 ?\n\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\n\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\t\treturn elem.nodeType === 1;\n\t\t}));\n};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i,\n\t\t\tlen = this.length,\n\t\t\tret = [],\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\n\t\treturn ret;\n\t},\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\n\t},\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\n\t},\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t}\n});\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n\tinit = jQuery.fn.init = function( selector, context ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector[0] === \"<\" && selector[ selector.length - 1 ] === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\n\t\t\t\t\t// Option to run scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[1],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Support: Blackberry 4.6\n\t\t\t\t\t// gEBID returns nodes no longer in the document (#6963)\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn typeof rootjQuery.ready !== \"undefined\" ?\n\t\t\t\trootjQuery.ready( selector ) :\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector( jQuery );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\nrootjQuery = jQuery( document );\n\n\nvar rparentsprev = /^(?:parents|prev(?:Until|All))/,\n\t// Methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.extend({\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\ttruncate = until !== undefined;\n\n\t\twhile ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tif ( truncate && jQuery( elem ).is( until ) ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmatched.push( elem );\n\t\t\t}\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar matched = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tmatched.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn matched;\n\t}\n});\n\njQuery.fn.extend({\n\thas: function( target ) {\n\t\tvar targets = jQuery( target, this ),\n\t\t\tl = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tmatched = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\n\t\t\t\t// Always skip document fragments\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\n\t\t\t\t\tpos.index(cur) > -1 :\n\n\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\n\n\t\t\t\t\tmatched.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );\n\t},\n\n\t// Determine the position of an element within the set\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// Index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn indexOf.call( jQuery( elem ), this[ 0 ] );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call( this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ] : elem\n\t\t);\n\t},\n\n\tadd: function( selector, context ) {\n\t\treturn this.pushStack(\n\t\t\tjQuery.unique(\n\t\t\t\tjQuery.merge( this.get(), jQuery( selector, context ) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling( cur, dir ) {\n\twhile ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn elem.contentDocument || jQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar matched = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tmatched = jQuery.filter( selector, matched );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tjQuery.unique( matched );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched );\n\t};\n});\nvar rnotwhite = (/\\S+/g);\n\n\n\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// Flag to know if list is currently firing\n\t\tfiring,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\tfiringLength = 0;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ]\n\t\t\tdeferred[ tuple[0] ] = function() {\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;\n\t\t\t\t\tif ( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// Add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\n\n\n// The deferred used on DOM ready\nvar readyList;\n\njQuery.fn.ready = function( fn ) {\n\t// Add the callback\n\tjQuery.ready.promise().done( fn );\n\n\treturn this;\n};\n\njQuery.extend({\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.triggerHandler ) {\n\t\t\tjQuery( document ).triggerHandler( \"ready\" );\n\t\t\tjQuery( document ).off( \"ready\" );\n\t\t}\n\t}\n});\n\n/**\n * The ready event handler and self cleanup method\n */\nfunction completed() {\n\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\n\twindow.removeEventListener( \"load\", completed, false );\n\tjQuery.ready();\n}\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// We once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready );\n\n\t\t} else {\n\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", completed, false );\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Kick off the DOM ready check even if the user does not\njQuery.ready.promise();\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\nvar access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\tvar i = 0,\n\t\tlen = elems.length,\n\t\tbulk = key == null;\n\n\t// Sets many values\n\tif ( jQuery.type( key ) === \"object\" ) {\n\t\tchainable = true;\n\t\tfor ( i in key ) {\n\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\n\t\t}\n\n\t// Sets one value\n\t} else if ( value !== undefined ) {\n\t\tchainable = true;\n\n\t\tif ( !jQuery.isFunction( value ) ) {\n\t\t\traw = true;\n\t\t}\n\n\t\tif ( bulk ) {\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif ( raw ) {\n\t\t\t\tfn.call( elems, value );\n\t\t\t\tfn = null;\n\n\t\t\t// ...except when executing function values\n\t\t\t} else {\n\t\t\t\tbulk = fn;\n\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif ( fn ) {\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn chainable ?\n\t\telems :\n\n\t\t// Gets\n\t\tbulk ?\n\t\t\tfn.call( elems ) :\n\t\t\tlen ? fn( elems[0], key ) : emptyGet;\n};\n\n\n/**\n * Determines whether an object can have data\n */\njQuery.acceptData = function( owner ) {\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\t/* jshint -W018 */\n\treturn owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n};\n\n\nfunction Data() {\n\t// Support: Android<4,\n\t// Old WebKit does not have Object.preventExtensions/freeze method,\n\t// return new empty object instead with no [[set]] accessor\n\tObject.defineProperty( this.cache = {}, 0, {\n\t\tget: function() {\n\t\t\treturn {};\n\t\t}\n\t});\n\n\tthis.expando = jQuery.expando + Data.uid++;\n}\n\nData.uid = 1;\nData.accepts = jQuery.acceptData;\n\nData.prototype = {\n\tkey: function( owner ) {\n\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t// but we should not, see #8335.\n\t\t// Always return the key for a frozen object.\n\t\tif ( !Data.accepts( owner ) ) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar descriptor = {},\n\t\t\t// Check if the owner object already has a cache key\n\t\t\tunlock = owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif ( !unlock ) {\n\t\t\tunlock = Data.uid++;\n\n\t\t\t// Secure it in a non-enumerable, non-writable property\n\t\t\ttry {\n\t\t\t\tdescriptor[ this.expando ] = { value: unlock };\n\t\t\t\tObject.defineProperties( owner, descriptor );\n\n\t\t\t// Support: Android<4\n\t\t\t// Fallback to a less secure definition\n\t\t\t} catch ( e ) {\n\t\t\t\tdescriptor[ this.expando ] = unlock;\n\t\t\t\tjQuery.extend( owner, descriptor );\n\t\t\t}\n\t\t}\n\n\t\t// Ensure the cache object\n\t\tif ( !this.cache[ unlock ] ) {\n\t\t\tthis.cache[ unlock ] = {};\n\t\t}\n\n\t\treturn unlock;\n\t},\n\tset: function( owner, data, value ) {\n\t\tvar prop,\n\t\t\t// There may be an unlock assigned to this node,\n\t\t\t// if there is no entry for this \"owner\", create one inline\n\t\t\t// and set the unlock as though an owner entry had always existed\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\t// Handle: [ owner, key, value ] args\n\t\tif ( typeof data === \"string\" ) {\n\t\t\tcache[ data ] = value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t} else {\n\t\t\t// Fresh assignments by object are shallow copied\n\t\t\tif ( jQuery.isEmptyObject( cache ) ) {\n\t\t\t\tjQuery.extend( this.cache[ unlock ], data );\n\t\t\t// Otherwise, copy the properties one-by-one to the cache object\n\t\t\t} else {\n\t\t\t\tfor ( prop in data ) {\n\t\t\t\t\tcache[ prop ] = data[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function( owner, key ) {\n\t\t// Either a valid cache is found, or will be created.\n\t\t// New caches will be created and the unlock returned,\n\t\t// allowing direct access to the newly created\n\t\t// empty data object. A valid owner object must be provided.\n\t\tvar cache = this.cache[ this.key( owner ) ];\n\n\t\treturn key === undefined ?\n\t\t\tcache : cache[ key ];\n\t},\n\taccess: function( owner, key, value ) {\n\t\tvar stored;\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the \"read\" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif ( key === undefined ||\n\t\t\t\t((key && typeof key === \"string\") && value === undefined) ) {\n\n\t\t\tstored = this.get( owner, key );\n\n\t\t\treturn stored !== undefined ?\n\t\t\t\tstored : this.get( owner, jQuery.camelCase(key) );\n\t\t}\n\n\t\t// [*]When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set( owner, key, value );\n\n\t\t// Since the \"set\" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value !== undefined ? value : key;\n\t},\n\tremove: function( owner, key ) {\n\t\tvar i, name, camel,\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\tif ( key === undefined ) {\n\t\t\tthis.cache[ unlock ] = {};\n\n\t\t} else {\n\t\t\t// Support array or space separated string of keys\n\t\t\tif ( jQuery.isArray( key ) ) {\n\t\t\t\t// If \"name\" is an array of keys...\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\n\t\t\t\t// keys will be converted to camelCase.\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\n\t\t\t\t// both plain key and camelCase key. #12786\n\t\t\t\t// This will only penalize the array argument path.\n\t\t\t\tname = key.concat( key.map( jQuery.camelCase ) );\n\t\t\t} else {\n\t\t\t\tcamel = jQuery.camelCase( key );\n\t\t\t\t// Try the string as a key before any manipulation\n\t\t\t\tif ( key in cache ) {\n\t\t\t\t\tname = [ key, camel ];\n\t\t\t\t} else {\n\t\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\t\tname = camel;\n\t\t\t\t\tname = name in cache ?\n\t\t\t\t\t\t[ name ] : ( name.match( rnotwhite ) || [] );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ti = name.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete cache[ name[ i ] ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function( owner ) {\n\t\treturn !jQuery.isEmptyObject(\n\t\t\tthis.cache[ owner[ this.expando ] ] || {}\n\t\t);\n\t},\n\tdiscard: function( owner ) {\n\t\tif ( owner[ this.expando ] ) {\n\t\t\tdelete this.cache[ owner[ this.expando ] ];\n\t\t}\n\t}\n};\nvar data_priv = new Data();\n\nvar data_user = new Data();\n\n\n\n//\tImplementation Summary\n//\n//\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n//\t2. Improve the module's maintainability by reducing the storage\n//\t\tpaths to a single mechanism.\n//\t3. Use the same single mechanism to support \"private\" and \"user\" data.\n//\t4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n//\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n//\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n\nvar rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\nfunction dataAttr( elem, key, data ) {\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\t\tname = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\t\tdata === \"false\" ? false :\n\t\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tdata_user.set( elem, key, data );\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend({\n\thasData: function( elem ) {\n\t\treturn data_user.hasData( elem ) || data_priv.hasData( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn data_user.access( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\tdata_user.remove( elem, name );\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to data_priv methods, these can be deprecated.\n\t_data: function( elem, name, data ) {\n\t\treturn data_priv.access( elem, name, data );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\tdata_priv.remove( elem, name );\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar i, name, data,\n\t\t\telem = this[ 0 ],\n\t\t\tattrs = elem && elem.attributes;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = data_user.get( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !data_priv.get( elem, \"hasDataAttrs\" ) ) {\n\t\t\t\t\ti = attrs.length;\n\t\t\t\t\twhile ( i-- ) {\n\n\t\t\t\t\t\t// Support: IE11+\n\t\t\t\t\t\t// The attrs elements can be null (#14894)\n\t\t\t\t\t\tif ( attrs[ i ] ) {\n\t\t\t\t\t\t\tname = attrs[ i ].name;\n\t\t\t\t\t\t\tif ( name.indexOf( \"data-\" ) === 0 ) {\n\t\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\n\t\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdata_priv.set( elem, \"hasDataAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tdata_user.set( this, key );\n\t\t\t});\n\t\t}\n\n\t\treturn access( this, function( value ) {\n\t\t\tvar data,\n\t\t\t\tcamelKey = jQuery.camelCase( key );\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem = this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif ( elem && value === undefined ) {\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key as-is\n\t\t\t\tdata = data_user.get( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key camelized\n\t\t\t\tdata = data_user.get( elem, camelKey );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to \"discover\" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata = dataAttr( elem, camelKey, undefined );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn't exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each(function() {\n\t\t\t\t// First, attempt to store a copy or reference of any\n\t\t\t\t// data that might've been store with a camelCased key.\n\t\t\t\tvar data = data_user.get( this, camelKey );\n\n\t\t\t\t// For HTML5 data-* attribute interop, we have to\n\t\t\t\t// store property names with dashes in a camelCase form.\n\t\t\t\t// This might not apply to all properties...*\n\t\t\t\tdata_user.set( this, camelKey, value );\n\n\t\t\t\t// *... In the case of properties that might _actually_\n\t\t\t\t// have dashes, we need to also store a copy of that\n\t\t\t\t// unchanged property.\n\t\t\t\tif ( key.indexOf(\"-\") !== -1 && data !== undefined ) {\n\t\t\t\t\tdata_user.set( this, key, value );\n\t\t\t\t}\n\t\t\t});\n\t\t}, null, value, arguments.length > 1, null, true );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tdata_user.remove( this, key );\n\t\t});\n\t}\n});\n\n\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = data_priv.get( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray( data ) ) {\n\t\t\t\t\tqueue = data_priv.access( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// Clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// Not public - generate a queueHooks object, or return the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn data_priv.get( elem, key ) || data_priv.access( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tdata_priv.remove( elem, [ type + \"queue\", key ] );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// Ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile ( i-- ) {\n\t\t\ttmp = data_priv.get( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar pnum = (/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/).source;\n\nvar cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\nvar isHidden = function( elem, el ) {\n\t\t// isHidden might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem = el || elem;\n\t\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n\t};\n\nvar rcheckableType = (/^(?:checkbox|radio)$/i);\n\n\n\n(function() {\n\tvar fragment = document.createDocumentFragment(),\n\t\tdiv = fragment.appendChild( document.createElement( \"div\" ) ),\n\t\tinput = document.createElement( \"input\" );\n\n\t// Support: Safari<=5.1\n\t// Check state lost if the name is set (#11217)\n\t// Support: Windows Web Apps (WWA)\n\t// `name` and `type` must use .setAttribute for WWA (#14901)\n\tinput.setAttribute( \"type\", \"radio\" );\n\tinput.setAttribute( \"checked\", \"checked\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tdiv.appendChild( input );\n\n\t// Support: Safari<=5.1, Android<4.2\n\t// Older WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE<=11+\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\tdiv.innerHTML = \"<textarea>x</textarea>\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n})();\nvar strundefined = typeof undefined;\n\n\n\nsupport.focusinBubbles = \"onfocusin\" in window;\n\n\nvar\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.get( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !(events = elemData.events) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !(eventHandle = elemData.handle) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply( elem, arguments ) : undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !(handlers = events[ type ]) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.hasData( elem ) && data_priv.get( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\t\t\tdata_priv.remove( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf(\".\") >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join(\".\");\n\t\tevent.namespace_re = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( data_priv.get( cur, \"events\" ) || {} )[ event.type ] && data_priv.get( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && jQuery.acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\n\t\t\t\tjQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\telem[ type ]();\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, j, ret, matched, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\targs = slice.call( arguments ),\n\t\t\thandlers = ( data_priv.get( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or 2) have namespace(s)\n\t\t\t\t// a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, matches, sel, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG <use> instance trees (#13180)\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\n\n\t\t\tfor ( ; cur !== this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.disabled !== true || event.type !== \"click\" ) {\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matches[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar eventDoc, doc, body,\n\t\t\t\tbutton = original.button;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop, copy,\n\t\t\ttype = event.type,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = this.fixHooks[ type ];\n\n\t\tif ( !fixHook ) {\n\t\t\tthis.fixHooks[ type ] = fixHook =\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\n\t\t\t\t{};\n\t\t}\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = new jQuery.Event( originalEvent );\n\n\t\ti = copy.length;\n\t\twhile ( i-- ) {\n\t\t\tprop = copy[ i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Support: Cordova 2.5 (WebKit) (#13255)\n\t\t// All events should have a target; Cordova deviceready doesn't\n\t\tif ( !event.target ) {\n\t\t\tevent.target = document;\n\t\t}\n\n\t\t// Support: Safari 6.0+, Chrome<28\n\t\t// Target should not be a text node (#504, #13143)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tfocus: {\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\n\t\t\t\t\tthis.focus();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\t\tclick: {\n\t\t\t// For checkbox, fire native event so checked state will be right\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this.type === \"checkbox\" && this.click && jQuery.nodeName( this, \"input\" ) ) {\n\t\t\t\t\tthis.click();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\n\t\t\t_default: function( event ) {\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn't alert if the returnValue field is not set.\n\t\t\t\tif ( event.result !== undefined && event.originalEvent ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\njQuery.removeEvent = function( elem, type, handle ) {\n\tif ( elem.removeEventListener ) {\n\t\telem.removeEventListener( type, handle, false );\n\t}\n};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = src.defaultPrevented ||\n\t\t\t\tsrc.defaultPrevented === undefined &&\n\t\t\t\t// Support: Android<4.0\n\t\t\t\tsrc.returnValue === false ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tif ( e && e.preventDefault ) {\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tif ( e && e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\n\t\tif ( e && e.stopImmediatePropagation ) {\n\t\t\te.stopImmediatePropagation();\n\t\t}\n\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// Support: Chrome 15+\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\",\n\tpointerenter: \"pointerover\",\n\tpointerleave: \"pointerout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// Support: Firefox, Chrome, Safari\n// Create \"bubbling\" focus and blur events\nif ( !support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler on the document while someone wants focusin/focusout\n\t\tvar handler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix );\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t\tdata_priv.access( doc, fix, ( attaches || 0 ) + 1 );\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix ) - 1;\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.removeEventListener( orig, handler, true );\n\t\t\t\t\tdata_priv.remove( doc, fix );\n\n\t\t\t\t} else {\n\t\t\t\t\tdata_priv.access( doc, fix, attaches );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar origFn, type;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) {\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[0];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n});\n\n\nvar\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\n\trscriptTypeMasked = /^true\\/(.*)/,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n\t// We have to close these tags to support XHTML (#13200)\n\twrapMap = {\n\n\t\t// Support: IE9\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\tcol: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t\t_default: [ 0, \"\", \"\" ]\n\t};\n\n// Support: IE9\nwrapMap.optgroup = wrapMap.option;\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// Support: 1.x compatibility\n// Manipulating tables requires a tbody\nfunction manipulationTarget( elem, content ) {\n\treturn jQuery.nodeName( elem, \"table\" ) &&\n\t\tjQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, \"tr\" ) ?\n\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\n\t\telem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = (elem.getAttribute(\"type\") !== null) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tvar match = rscriptTypeMasked.exec( elem.type );\n\n\tif ( match ) {\n\t\telem.type = match[ 1 ];\n\t} else {\n\t\telem.removeAttribute(\"type\");\n\t}\n\n\treturn elem;\n}\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar i = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\tdata_priv.set(\n\t\t\telems[ i ], \"globalEval\", !refElements || data_priv.get( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\tvar i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;\n\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif ( data_priv.hasData( src ) ) {\n\t\tpdataOld = data_priv.access( src );\n\t\tpdataCur = data_priv.set( dest, pdataOld );\n\t\tevents = pdataOld.events;\n\n\t\tif ( events ) {\n\t\t\tdelete pdataCur.handle;\n\t\t\tpdataCur.events = {};\n\n\t\t\tfor ( type in events ) {\n\t\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif ( data_user.hasData( src ) ) {\n\t\tudataOld = data_user.access( src );\n\t\tudataCur = jQuery.extend( {}, udataOld );\n\n\t\tdata_user.set( dest, udataCur );\n\t}\n}\n\nfunction getAll( context, tag ) {\n\tvar ret = context.getElementsByTagName ? context.getElementsByTagName( tag || \"*\" ) :\n\t\t\tcontext.querySelectorAll ? context.querySelectorAll( tag || \"*\" ) :\n\t\t\t[];\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], ret ) :\n\t\tret;\n}\n\n// Fix IE bugs, see support tests\nfunction fixInput( src, dest ) {\n\tvar nodeName = dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\tdest.checked = src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone = elem.cloneNode( true ),\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t// Fix IE cloning issues\n\t\tif ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&\n\t\t\t\t!jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\tfixInput( srcElements[ i ], destElements[ i ] );\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[ i ], destElements[ i ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tbuildFragment: function( elems, context, scripts, selection ) {\n\t\tvar elem, tmp, tag, wrap, contains, j,\n\t\t\tfragment = context.createDocumentFragment(),\n\t\t\tnodes = [],\n\t\t\ti = 0,\n\t\t\tl = elems.length;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\telem = elems[ i ];\n\n\t\t\tif ( elem || elem === 0 ) {\n\n\t\t\t\t// Add nodes directly\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\n\t\t\t\t\t// Support: QtWebKit, PhantomJS\n\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t\t// Convert non-html into a text node\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t\t// Convert html into DOM nodes\n\t\t\t\t} else {\n\t\t\t\t\ttmp = tmp || fragment.appendChild( context.createElement(\"div\") );\n\n\t\t\t\t\t// Deserialize a standard representation\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\t\ttmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[ 2 ];\n\n\t\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\t\tj = wrap[ 0 ];\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: QtWebKit, PhantomJS\n\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t\t// Remember the top-level container\n\t\t\t\t\ttmp = fragment.firstChild;\n\n\t\t\t\t\t// Ensure the created nodes are orphaned (#12392)\n\t\t\t\t\ttmp.textContent = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove wrapper from fragment\n\t\tfragment.textContent = \"\";\n\n\t\ti = 0;\n\t\twhile ( (elem = nodes[ i++ ]) ) {\n\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\n\t\t\t// that element, do not do anything\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t\t// Append to fragment\n\t\t\ttmp = getAll( fragment.appendChild( elem ), \"script\" );\n\n\t\t\t// Preserve script evaluation history\n\t\t\tif ( contains ) {\n\t\t\t\tsetGlobalEval( tmp );\n\t\t\t}\n\n\t\t\t// Capture executables\n\t\t\tif ( scripts ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\t\tscripts.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn fragment;\n\t},\n\n\tcleanData: function( elems ) {\n\t\tvar data, elem, type, key,\n\t\t\tspecial = jQuery.event.special,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[ i ]) !== undefined; i++ ) {\n\t\t\tif ( jQuery.acceptData( elem ) ) {\n\t\t\t\tkey = elem[ data_priv.expando ];\n\n\t\t\t\tif ( key && (data = data_priv.cache[ key ]) ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( data_priv.cache[ key ] ) {\n\t\t\t\t\t\t// Discard any remaining `private` data\n\t\t\t\t\t\tdelete data_priv.cache[ key ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Discard any remaining `user` data\n\t\t\tdelete data_user.cache[ elem[ data_user.expando ] ];\n\t\t}\n\t}\n});\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().each(function() {\n\t\t\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\t\t\tthis.textContent = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t});\n\t},\n\n\tremove: function( selector, keepData /* Internal Use Only */ ) {\n\t\tvar elem,\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\n\t\t\t}\n\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\n\t\t\t\t}\n\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map(function() {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\tvar elem = this[ 0 ] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined && elem.nodeType === 1 ) {\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\t\telem = this[ i ] || {};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch( e ) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar arg = arguments[ 0 ];\n\n\t\t// Make the changes, replacing each context element with the new content\n\t\tthis.domManip( arguments, function( elem ) {\n\t\t\targ = this.parentNode;\n\n\t\t\tjQuery.cleanData( getAll( this ) );\n\n\t\t\tif ( arg ) {\n\t\t\t\targ.replaceChild( elem, this );\n\t\t\t}\n\t\t});\n\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\n\t\treturn arg && (arg.length || arg.nodeType) ? this : this.remove();\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, callback ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = concat.apply( [], args );\n\n\t\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tset = this,\n\t\t\tiNoClone = l - 1,\n\t\t\tvalue = args[ 0 ],\n\t\t\tisFunction = jQuery.isFunction( value );\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( isFunction ||\n\t\t\t\t( l > 1 && typeof value === \"string\" &&\n\t\t\t\t\t!support.checkClone && rchecked.test( value ) ) ) {\n\t\t\treturn this.each(function( index ) {\n\t\t\t\tvar self = set.eq( index );\n\t\t\t\tif ( isFunction ) {\n\t\t\t\t\targs[ 0 ] = value.call( this, index, self.html() );\n\t\t\t\t}\n\t\t\t\tself.domManip( args, callback );\n\t\t\t});\n\t\t}\n\n\t\tif ( l ) {\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\t\thasScripts = scripts.length;\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\tnode = fragment;\n\n\t\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcallback.call( this[ i ], node, i );\n\t\t\t\t}\n\n\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t\t// Reenable scripts\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t\t!data_priv.access( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\t\tif ( node.src ) {\n\t\t\t\t\t\t\t\t// Optional AJAX dependency, but won't run scripts if not present\n\t\t\t\t\t\t\t\tif ( jQuery._evalUrl ) {\n\t\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.globalEval( node.textContent.replace( rcleanScript, \"\" ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1,\n\t\t\ti = 0;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone( true );\n\t\t\tjQuery( insert[ i ] )[ original ]( elems );\n\n\t\t\t// Support: QtWebKit\n\t\t\t// .get() because push.apply(_, arraylike) throws\n\t\t\tpush.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\n\nvar iframe,\n\telemdisplay = {};\n\n/**\n * Retrieve the actual display of a element\n * @param {String} name nodeName of the element\n * @param {Object} doc Document object\n */\n// Called only from within defaultDisplay\nfunction actualDisplay( name, doc ) {\n\tvar style,\n\t\telem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\n\n\t\t// getDefaultComputedStyle might be reliably used only on attached element\n\t\tdisplay = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?\n\n\t\t\t// Use of this method is a temporary fix (more like optimization) until something better comes along,\n\t\t\t// since it was removed from specification and supported only in FF\n\t\t\tstyle.display : jQuery.css( elem[ 0 ], \"display\" );\n\n\t// We don't have any data stored on the element,\n\t// so use \"detach\" method as fast way to get rid of the element\n\telem.detach();\n\n\treturn display;\n}\n\n/**\n * Try to determine the default display value of an element\n * @param {String} nodeName\n */\nfunction defaultDisplay( nodeName ) {\n\tvar doc = document,\n\t\tdisplay = elemdisplay[ nodeName ];\n\n\tif ( !display ) {\n\t\tdisplay = actualDisplay( nodeName, doc );\n\n\t\t// If the simple way fails, read from inside an iframe\n\t\tif ( display === \"none\" || !display ) {\n\n\t\t\t// Use the already-created iframe if possible\n\t\t\tiframe = (iframe || jQuery( \"<iframe frameborder='0' width='0' height='0'/>\" )).appendTo( doc.documentElement );\n\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n\t\t\tdoc = iframe[ 0 ].contentDocument;\n\n\t\t\t// Support: IE\n\t\t\tdoc.write();\n\t\t\tdoc.close();\n\n\t\t\tdisplay = actualDisplay( nodeName, doc );\n\t\t\tiframe.detach();\n\t\t}\n\n\t\t// Store the correct default display\n\t\telemdisplay[ nodeName ] = display;\n\t}\n\n\treturn display;\n}\nvar rmargin = (/^margin/);\n\nvar rnumnonpx = new RegExp( \"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\" );\n\nvar getStyles = function( elem ) {\n\t\t// Support: IE<=11+, Firefox<=30+ (#15098, #14150)\n\t\t// IE throws on elements created in popups\n\t\t// FF meanwhile throws on frame elements through \"defaultView.getComputedStyle\"\n\t\tif ( elem.ownerDocument.defaultView.opener ) {\n\t\t\treturn elem.ownerDocument.defaultView.getComputedStyle( elem, null );\n\t\t}\n\n\t\treturn window.getComputedStyle( elem, null );\n\t};\n\n\n\nfunction curCSS( elem, name, computed ) {\n\tvar width, minWidth, maxWidth, ret,\n\t\tstyle = elem.style;\n\n\tcomputed = computed || getStyles( elem );\n\n\t// Support: IE9\n\t// getPropertyValue is only needed for .css('filter') (#12537)\n\tif ( computed ) {\n\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\t}\n\n\tif ( computed ) {\n\n\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\tret = jQuery.style( elem, name );\n\t\t}\n\n\t\t// Support: iOS < 6\n\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\twidth = style.width;\n\t\t\tminWidth = style.minWidth;\n\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\tret = computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width = width;\n\t\t\tstyle.minWidth = minWidth;\n\t\t\tstyle.maxWidth = maxWidth;\n\t\t}\n\t}\n\n\treturn ret !== undefined ?\n\t\t// Support: IE\n\t\t// IE returns zIndex value as an integer.\n\t\tret + \"\" :\n\t\tret;\n}\n\n\nfunction addGetHookIf( conditionFn, hookFn ) {\n\t// Define the hook, we'll check on the first run if it's really needed.\n\treturn {\n\t\tget: function() {\n\t\t\tif ( conditionFn() ) {\n\t\t\t\t// Hook not needed (or it's not possible to use it due\n\t\t\t\t// to missing dependency), remove it.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\t\t\treturn (this.get = hookFn).apply( this, arguments );\n\t\t}\n\t};\n}\n\n\n(function() {\n\tvar pixelPositionVal, boxSizingReliableVal,\n\t\tdocElem = document.documentElement,\n\t\tcontainer = document.createElement( \"div\" ),\n\t\tdiv = document.createElement( \"div\" );\n\n\tif ( !div.style ) {\n\t\treturn;\n\t}\n\n\t// Support: IE9-11+\n\t// Style of cloned element affects source element cloned (#8908)\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\tcontainer.style.cssText = \"border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;\" +\n\t\t\"position:absolute\";\n\tcontainer.appendChild( div );\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they're executed at the same time to save the second computation.\n\tfunction computePixelPositionAndBoxSizingReliable() {\n\t\tdiv.style.cssText =\n\t\t\t// Support: Firefox<29, Android 2.3\n\t\t\t// Vendor-prefix box-sizing\n\t\t\t\"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;\" +\n\t\t\t\"box-sizing:border-box;display:block;margin-top:1%;top:1%;\" +\n\t\t\t\"border:1px;padding:1px;width:4px;position:absolute\";\n\t\tdiv.innerHTML = \"\";\n\t\tdocElem.appendChild( container );\n\n\t\tvar divStyle = window.getComputedStyle( div, null );\n\t\tpixelPositionVal = divStyle.top !== \"1%\";\n\t\tboxSizingReliableVal = divStyle.width === \"4px\";\n\n\t\tdocElem.removeChild( container );\n\t}\n\n\t// Support: node.js jsdom\n\t// Don't assume that getComputedStyle is a property of the global object\n\tif ( window.getComputedStyle ) {\n\t\tjQuery.extend( support, {\n\t\t\tpixelPosition: function() {\n\n\t\t\t\t// This test is executed only once but we still do memoizing\n\t\t\t\t// since we can use the boxSizingReliable pre-computing.\n\t\t\t\t// No need to check if the test was already performed, though.\n\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\treturn pixelPositionVal;\n\t\t\t},\n\t\t\tboxSizingReliable: function() {\n\t\t\t\tif ( boxSizingReliableVal == null ) {\n\t\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\t}\n\t\t\t\treturn boxSizingReliableVal;\n\t\t\t},\n\t\t\treliableMarginRight: function() {\n\n\t\t\t\t// Support: Android 2.3\n\t\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t\t// gets computed margin-right based on width of container. (#3333)\n\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t// This support function is only executed once so no memoizing is needed.\n\t\t\t\tvar ret,\n\t\t\t\t\tmarginDiv = div.appendChild( document.createElement( \"div\" ) );\n\n\t\t\t\t// Reset CSS: box-sizing; display; margin; border; padding\n\t\t\t\tmarginDiv.style.cssText = div.style.cssText =\n\t\t\t\t\t// Support: Firefox<29, Android 2.3\n\t\t\t\t\t// Vendor-prefix box-sizing\n\t\t\t\t\t\"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;\" +\n\t\t\t\t\t\"box-sizing:content-box;display:block;margin:0;border:0;padding:0\";\n\t\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\t\tdiv.style.width = \"1px\";\n\t\t\t\tdocElem.appendChild( container );\n\n\t\t\t\tret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );\n\n\t\t\t\tdocElem.removeChild( container );\n\t\t\t\tdiv.removeChild( marginDiv );\n\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t});\n\t}\n})();\n\n\n// A method for quickly swapping in/out CSS properties to get correct calculations.\njQuery.swap = function( elem, options, callback, args ) {\n\tvar ret, name,\n\t\told = {};\n\n\t// Remember the old values, and insert the new ones\n\tfor ( name in options ) {\n\t\told[ name ] = elem.style[ name ];\n\t\telem.style[ name ] = options[ name ];\n\t}\n\n\tret = callback.apply( elem, args || [] );\n\n\t// Revert the old values\n\tfor ( name in options ) {\n\t\telem.style[ name ] = old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar\n\t// Swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trnumsplit = new RegExp( \"^(\" + pnum + \")(.*)$\", \"i\" ),\n\trrelNum = new RegExp( \"^([+-])=(\" + pnum + \")\", \"i\" ),\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: \"0\",\n\t\tfontWeight: \"400\"\n\t},\n\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// Return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// Shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// Check for vendor prefixed names\n\tvar capName = name[0].toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// Both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// At this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t} else {\n\t\t\t// At this point, extra isn't content, so add padding\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// At this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar valueIsBorderBox = true,\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tstyles = getStyles( elem ),\n\t\tisBorderBox = jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t// Some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name, styles );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// Check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox &&\n\t\t\t( support.boxSizingReliable() || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// Use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles\n\t\t)\n\t) + \"px\";\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem, hidden,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvalues[ index ] = data_priv.get( elem, \"olddisplay\" );\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = data_priv.access( elem, \"olddisplay\", defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\t\t\thidden = isHidden( elem );\n\n\t\t\tif ( display !== \"none\" || !hidden ) {\n\t\t\t\tdata_priv.set( elem, \"olddisplay\", hidden ? display : jQuery.css( elem, \"display\" ) );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.extend({\n\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"flexGrow\": true,\n\t\t\"flexShrink\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t\"float\": \"cssFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// Gets hook for the prefixed version, then unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// Convert \"+=\" or \"-=\" to relative numbers (#7345)\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren't set (#7116)\n\t\t\tif ( value == null || value !== value ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// Support: IE9-11+\n\t\t\t// background-* props affect original clone's values\n\t\t\tif ( !support.clearCloneStyle && value === \"\" && name.indexOf( \"background\" ) === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\t\t\t\tstyle[ name ] = value;\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// Try prefixed name followed by the unprefixed name\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t// Convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Make numeric if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t}\n});\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\n\t\t\t\t// Certain elements can have dimension info if we invisibly show them\n\t\t\t\t// but it must have a current display style that would benefit\n\t\t\t\treturn rdisplayswap.test( jQuery.css( elem, \"display\" ) ) && elem.offsetWidth === 0 ?\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar styles = extra && getStyles( elem );\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\t\tstyles\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\n// Support: Android 2.3\njQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,\n\tfunction( elem, computed ) {\n\t\tif ( computed ) {\n\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\n\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// Assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn access( this, function( elem, name, value ) {\n\t\t\tvar styles, len,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( jQuery.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// Passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails.\n\t\t\t// Simple values such as \"10px\" are parsed to Float;\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as-is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// Use step hook for back compat.\n\t\t\t// Use cssHook if its there.\n\t\t\t// Use .style if available and use plain properties where available.\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE9\n// Panic based approach to setting things on disconnected nodes\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p * Math.PI ) / 2;\n\t}\n};\n\njQuery.fx = Tween.prototype.init;\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\n\n\n\nvar\n\tfxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [ function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t\t\t// Starting value computation is required for potential unit mismatches\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( start && start[ 3 ] !== unit ) {\n\t\t\t\t// Trust units reported by jQuery.css\n\t\t\t\tunit = unit || start[ 3 ];\n\n\t\t\t\t// Make sure we update the tween properties later on\n\t\t\t\tparts = parts || [];\n\n\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\tstart = +target || 1;\n\n\t\t\t\tdo {\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*.\n\t\t\t\t\t// Use string for doubling so we don't accidentally see scale as unchanged below\n\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t// Adjust and apply\n\t\t\t\t\tstart = start / scale;\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur(),\n\t\t\t\t// break the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t}\n\n\t\t\t// Update tween properties\n\t\t\tif ( parts ) {\n\t\t\t\tstart = tween.start = +start || +target || 0;\n\t\t\t\ttween.unit = unit;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[ 1 ] ?\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n\t\t\t\t\t+parts[ 2 ];\n\t\t\t}\n\n\t\t\treturn tween;\n\t\t} ]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t});\n\treturn ( fxNow = jQuery.now() );\n}\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\ti = 0,\n\t\tattrs = { height: type };\n\n\t// If we include width, step value is 1 to do all cssExpand values,\n\t// otherwise step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth ? 1 : 0;\n\tfor ( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\n\n\t\t\t// We're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter( elem, props, opts ) {\n\t/* jshint validthis: true */\n\tvar prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHidden( elem ),\n\t\tdataShow = data_priv.get( elem, \"fxshow\" );\n\n\t// Handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// Ensure the complete handler is called before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// Height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE9-10 do not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tdisplay = jQuery.css( elem, \"display\" );\n\n\t\t// Test default display if display is currently \"none\"\n\t\tcheckDisplay = display === \"none\" ?\n\t\t\tdata_priv.get( elem, \"olddisplay\" ) || defaultDisplay( elem.nodeName ) : display;\n\n\t\tif ( checkDisplay === \"inline\" && jQuery.css( elem, \"float\" ) === \"none\" ) {\n\t\t\tstyle.display = \"inline-block\";\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tanim.always(function() {\n\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t});\n\t}\n\n\t// show/hide pass\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\n\t\t\t\t// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden\n\t\t\t\tif ( value === \"show\" && dataShow && dataShow[ prop ] !== undefined ) {\n\t\t\t\t\thidden = true;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\n\t\t// Any non-fx value stops us from restoring the original display value\n\t\t} else {\n\t\t\tdisplay = undefined;\n\t\t}\n\t}\n\n\tif ( !jQuery.isEmptyObject( orig ) ) {\n\t\tif ( dataShow ) {\n\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\thidden = dataShow.hidden;\n\t\t\t}\n\t\t} else {\n\t\t\tdataShow = data_priv.access( elem, \"fxshow\", {} );\n\t\t}\n\n\t\t// Store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\n\t\t\tdata_priv.remove( elem, \"fxshow\" );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( prop in orig ) {\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t// If this is a noop like .hide().hide(), restore an overwritten display value\n\t} else if ( (display === \"none\" ? defaultDisplay( elem.nodeName ) : display) === \"inline\" ) {\n\t\tstyle.display = display;\n\t}\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// Not quite $.extend, this won't overwrite existing keys.\n\t\t\t// Reusing 'index' because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// Don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// Support: Android 2.3\n\t\t\t\t// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// If we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// Resolve when we played the last frame; otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// Normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// Show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// Animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || data_priv.get( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = data_priv.get( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Start the next in the queue if the last step wasn't forced.\n\t\t\t// Timers currently will call their complete callbacks, which\n\t\t\t// will dequeue but only if they were gotoEnd.\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tvar index,\n\t\t\t\tdata = data_priv.get( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// Enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// Empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// Look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.timers = [];\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ti = 0,\n\t\ttimers = jQuery.timers;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tjQuery.timers.push( timer );\n\tif ( timer() ) {\n\t\tjQuery.fx.start();\n\t} else {\n\t\tjQuery.timers.pop();\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.start = function() {\n\tif ( !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\n// http://blindsignals.com/index.php/2009/07/jquery-delay/\njQuery.fn.delay = function( time, type ) {\n\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\ttype = type || \"fx\";\n\n\treturn this.queue( type, function( next, hooks ) {\n\t\tvar timeout = setTimeout( next, time );\n\t\thooks.stop = function() {\n\t\t\tclearTimeout( timeout );\n\t\t};\n\t});\n};\n\n\n(function() {\n\tvar input = document.createElement( \"input\" ),\n\t\tselect = document.createElement( \"select\" ),\n\t\topt = select.appendChild( document.createElement( \"option\" ) );\n\n\tinput.type = \"checkbox\";\n\n\t// Support: iOS<=5.1, Android<=4.2+\n\t// Default value for a checkbox should be \"on\"\n\tsupport.checkOn = input.value !== \"\";\n\n\t// Support: IE<=11+\n\t// Must access selectedIndex to make default options select\n\tsupport.optSelected = opt.selected;\n\n\t// Support: Android<=2.3\n\t// Options inside disabled selects are incorrectly marked as disabled\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Support: IE<=11+\n\t// An input loses its value after becoming a radio\n\tinput = document.createElement( \"input\" );\n\tinput.value = \"t\";\n\tinput.type = \"radio\";\n\tsupport.radioValue = input.value === \"t\";\n})();\n\n\nvar nodeHook, boolHook,\n\tattrHandle = jQuery.expr.attrHandle;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tattr: function( elem, name, value ) {\n\t\tvar hooks, ret,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === strundefined ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\t\t\tret = jQuery.find.attr( elem, name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret == null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name, propName,\n\t\t\ti = 0,\n\t\t\tattrNames = value && value.match( rnotwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( (name = attrNames[i++]) ) {\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\n\t\t\t\t// Boolean attributes get special treatment (#10870)\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\n\t\t\t\t\t// Set corresponding property to false\n\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t}\n\n\t\t\t\telem.removeAttribute( name );\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !support.radioValue && value === \"radio\" &&\n\t\t\t\t\tjQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\telem.setAttribute( name, name );\n\t\t}\n\t\treturn name;\n\t}\n};\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\n\tvar getter = attrHandle[ name ] || jQuery.find.attr;\n\n\tattrHandle[ name ] = function( elem, name, isXML ) {\n\t\tvar ret, handle;\n\t\tif ( !isXML ) {\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle = attrHandle[ name ];\n\t\t\tattrHandle[ name ] = ret;\n\t\t\tret = getter( elem, name, isXML ) != null ?\n\t\t\t\tname.toLowerCase() :\n\t\t\t\tnull;\n\t\t\tattrHandle[ name ] = handle;\n\t\t}\n\t\treturn ret;\n\t};\n});\n\n\n\n\nvar rfocusable = /^(?:input|select|textarea|button)$/i;\n\njQuery.fn.extend({\n\tprop: function( name, value ) {\n\t\treturn access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tdelete this[ jQuery.propFix[ name ] || name ];\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// Don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\n\t\t\t\tret :\n\t\t\t\t( elem[ name ] = value );\n\n\t\t} else {\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\n\t\t\t\tret :\n\t\t\t\telem[ name ];\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\treturn elem.hasAttribute( \"tabindex\" ) || rfocusable.test( elem.nodeName ) || elem.href ?\n\t\t\t\t\telem.tabIndex :\n\t\t\t\t\t-1;\n\t\t\t}\n\t\t}\n\t}\n});\n\nif ( !support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent && parent.parentNode ) {\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t};\n}\n\njQuery.each([\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n});\n\n\n\n\nvar rclass = /[\\t\\r\\n\\f]/g;\n\njQuery.fn.extend({\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\n\t\tif ( proceed ) {\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\" \"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = jQuery.trim( cur );\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\t\tif ( proceed ) {\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\"\"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = value ? jQuery.trim( cur ) : \"\";\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value;\n\n\t\tif ( typeof stateVal === \"boolean\" && type === \"string\" ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// Toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tclassNames = value.match( rnotwhite ) || [];\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// Check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( type === strundefined || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tdata_priv.set( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed `false`,\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tthis.className = this.className || value === false ? \"\" : data_priv.get( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n});\n\n\n\n\nvar rreturn = /\\r/g;\n\njQuery.fn.extend({\n\tval: function( value ) {\n\t\tvar hooks, ret, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// Handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// Handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map( val, function( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\n\t\t\t\treturn val != null ?\n\t\t\t\t\tval :\n\t\t\t\t\t// Support: IE10-11+\n\t\t\t\t\t// option.text throws exceptions (#14686, #14858)\n\t\t\t\t\tjQuery.trim( jQuery.text( elem ) );\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// IE6-9 doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( support.optDisabled ? !option.disabled : option.getAttribute( \"disabled\" ) === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Radios and checkboxes getter/setter\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t};\n\t}\n});\n\n\n\n\n// Return jQuery for attributes-only inclusion\n\n\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n});\n\njQuery.fn.extend({\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t}\n});\n\n\nvar nonce = jQuery.now();\n\nvar rquery = (/\\?/);\n\n\n\n// Support: Android 2.3\n// Workaround failure to string-cast null input\njQuery.parseJSON = function( data ) {\n\treturn JSON.parse( data + \"\" );\n};\n\n\n// Cross-browser xml parsing\njQuery.parseXML = function( data ) {\n\tvar xml, tmp;\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\n\t// Support: IE9\n\ttry {\n\t\ttmp = new DOMParser();\n\t\txml = tmp.parseFromString( data, \"text/xml\" );\n\t} catch ( e ) {\n\t\txml = undefined;\n\t}\n\n\tif ( !xml || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\tjQuery.error( \"Invalid XML: \" + data );\n\t}\n\treturn xml;\n};\n\n\nvar\n\trhash = /#.*$/,\n\trts = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trurl = /^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat( \"*\" ),\n\n\t// Document location\n\tajaxLocation = window.location.href,\n\n\t// Segment location into parts\n\tajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[0] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif ( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile ( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar transport,\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\t\t\t// Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" )\n\t\t\t.replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( rnotwhite ) || [ \"\" ];\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\t// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)\n\t\tfireGlobals = jQuery.event && s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\tcacheURL = s.url;\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\tcacheURL = ( s.url += ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\ts.url = rts.test( cacheURL ) ?\n\n\t\t\t\t\t// If there is already a '_' parameter, set its value\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + nonce++ ) :\n\n\t\t\t\t\t// Otherwise add one to the end\n\t\t\t\t\tcacheURL + ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + nonce++;\n\t\t\t}\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// Aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout(function() {\n\t\t\t\t\tjqXHR.abort(\"timeout\");\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Extract error from statusText and normalize for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// Shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t});\n\t};\n});\n\n\njQuery._evalUrl = function( url ) {\n\treturn jQuery.ajax({\n\t\turl: url,\n\t\ttype: \"GET\",\n\t\tdataType: \"script\",\n\t\tasync: false,\n\t\tglobal: false,\n\t\t\"throws\": true\n\t});\n};\n\n\njQuery.fn.extend({\n\twrapAll: function( html ) {\n\t\tvar wrap;\n\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[ 0 ] ) {\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );\n\n\t\t\tif ( this[ 0 ].parentNode ) {\n\t\t\t\twrap.insertBefore( this[ 0 ] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstElementChild ) {\n\t\t\t\t\telem = elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function( i ) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t}\n});\n\n\njQuery.expr.filters.hidden = function( elem ) {\n\t// Support: Opera <= 12.12\n\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\n\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0;\n};\njQuery.expr.filters.visible = function( elem ) {\n\treturn !jQuery.expr.filters.hidden( elem );\n};\n\n\n\n\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function() {\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t})\n\t\t.filter(function() {\n\t\t\tvar type = this.type;\n\n\t\t\t// Use .is( \":disabled\" ) so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !rcheckableType.test( type ) );\n\t\t})\n\t\t.map(function( i, elem ) {\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val ) {\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n\njQuery.ajaxSettings.xhr = function() {\n\ttry {\n\t\treturn new XMLHttpRequest();\n\t} catch( e ) {}\n};\n\nvar xhrId = 0,\n\txhrCallbacks = {},\n\txhrSuccessStatus = {\n\t\t// file protocol always yields status code 0, assume 200\n\t\t0: 200,\n\t\t// Support: IE9\n\t\t// #1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported = jQuery.ajaxSettings.xhr();\n\n// Support: IE9\n// Open requests must be manually aborted on unload (#5280)\n// See https://support.microsoft.com/kb/2856746 for more info\nif ( window.attachEvent ) {\n\twindow.attachEvent( \"onunload\", function() {\n\t\tfor ( var key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]();\n\t\t}\n\t});\n}\n\nsupport.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nsupport.ajax = xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport(function( options ) {\n\tvar callback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif ( support.cors || xhrSupported && !options.crossDomain ) {\n\t\treturn {\n\t\t\tsend: function( headers, complete ) {\n\t\t\t\tvar i,\n\t\t\t\t\txhr = options.xhr(),\n\t\t\t\t\tid = ++xhrId;\n\n\t\t\t\txhr.open( options.type, options.url, options.async, options.username, options.password );\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif ( options.xhrFields ) {\n\t\t\t\t\tfor ( i in options.xhrFields ) {\n\t\t\t\t\t\txhr[ i ] = options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif ( options.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\txhr.overrideMimeType( options.mimeType );\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\tif ( !options.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback = function( type ) {\n\t\t\t\t\treturn function() {\n\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\tdelete xhrCallbacks[ id ];\n\t\t\t\t\t\t\tcallback = xhr.onload = xhr.onerror = null;\n\n\t\t\t\t\t\t\tif ( type === \"abort\" ) {\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t} else if ( type === \"error\" ) {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\t// file: protocol always yields status 0; see #8605, #14207\n\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ] || xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\t\t\t\t\t\t\t\t\t// Support: IE9\n\t\t\t\t\t\t\t\t\t// Accessing binary-data responseText throws an exception\n\t\t\t\t\t\t\t\t\t// (#11426)\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText === \"string\" ? {\n\t\t\t\t\t\t\t\t\t\ttext: xhr.responseText\n\t\t\t\t\t\t\t\t\t} : undefined,\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload = callback();\n\t\t\t\txhr.onerror = callback(\"error\");\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback = xhrCallbacks[ id ] = callback(\"abort\");\n\n\t\t\t\ttry {\n\t\t\t\t\t// Do send the request (this may raise an exception)\n\t\t\t\t\txhr.send( options.hasContent && options.data || null );\n\t\t\t\t} catch ( e ) {\n\t\t\t\t\t// #14683: Only rethrow if this hasn't been notified as an error yet\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tthrow e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /(?:java|ecma)script/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and crossDomain\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function( s ) {\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function( _, complete ) {\n\t\t\t\tscript = jQuery(\"<script>\").prop({\n\t\t\t\t\tasync: true,\n\t\t\t\t\tcharset: s.scriptCharset,\n\t\t\t\t\tsrc: s.url\n\t\t\t\t}).on(\n\t\t\t\t\t\"load error\",\n\t\t\t\t\tcallback = function( evt ) {\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback = null;\n\t\t\t\t\t\tif ( evt ) {\n\t\t\t\t\t\t\tcomplete( evt.type === \"error\" ? 404 : 200, evt.type );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tdocument.head.appendChild( script[ 0 ] );\n\t\t\t},\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\n\n\n\n\n// data: string of html\n// context (optional): If specified, the fragment will be created in this context, defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML = function( data, context, keepScripts ) {\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\tif ( typeof context === \"boolean\" ) {\n\t\tkeepScripts = context;\n\t\tcontext = false;\n\t}\n\tcontext = context || document;\n\n\tvar parsed = rsingleTag.exec( data ),\n\t\tscripts = !keepScripts && [];\n\n\t// Single tag\n\tif ( parsed ) {\n\t\treturn [ context.createElement( parsed[1] ) ];\n\t}\n\n\tparsed = jQuery.buildFragment( [ data ], context, scripts );\n\n\tif ( scripts && scripts.length ) {\n\t\tjQuery( scripts ).remove();\n\t}\n\n\treturn jQuery.merge( [], parsed.childNodes );\n};\n\n\n// Keep a copy of the old load method\nvar _load = jQuery.fn.load;\n\n/**\n * Load a url into a page\n */\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = jQuery.trim( url.slice( off ) );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\t\ttype: type,\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t}).done(function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t}).complete( callback && function( jqXHR, status ) {\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t});\n\t}\n\n\treturn this;\n};\n\n\n\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ) {\n\tjQuery.fn[ type ] = function( fn ) {\n\t\treturn this.on( type, fn );\n\t};\n});\n\n\n\n\njQuery.expr.filters.animated = function( elem ) {\n\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\treturn elem === fn.elem;\n\t}).length;\n};\n\n\n\n\nvar docElem = window.document.documentElement;\n\n/**\n * Gets a window from an element\n */\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;\n}\n\njQuery.offset = {\n\tsetOffset: function( elem, options, i ) {\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition = jQuery.css( elem, \"position\" ),\n\t\t\tcurElem = jQuery( elem ),\n\t\t\tprops = {};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tcurOffset = curElem.offset();\n\t\tcurCSSTop = jQuery.css( elem, \"top\" );\n\t\tcurCSSLeft = jQuery.css( elem, \"left\" );\n\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n\t\t\t( curCSSTop + curCSSLeft ).indexOf(\"auto\") > -1;\n\n\t\t// Need to be able to calculate position if either\n\t\t// top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\njQuery.fn.extend({\n\toffset: function( options ) {\n\t\tif ( arguments.length ) {\n\t\t\treturn options === undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each(function( i ) {\n\t\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t\t});\n\t\t}\n\n\t\tvar docElem, win,\n\t\t\telem = this[ 0 ],\n\t\t\tbox = { top: 0, left: 0 },\n\t\t\tdoc = elem && elem.ownerDocument;\n\n\t\tif ( !doc ) {\n\t\t\treturn;\n\t\t}\n\n\t\tdocElem = doc.documentElement;\n\n\t\t// Make sure it's not a disconnected DOM node\n\t\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\t\treturn box;\n\t\t}\n\n\t\t// Support: BlackBerry 5, iOS 3 (original iPhone)\n\t\t// If we don't have gBCR, just use 0,0 rather than error\n\t\tif ( typeof elem.getBoundingClientRect !== strundefined ) {\n\t\t\tbox = elem.getBoundingClientRect();\n\t\t}\n\t\twin = getWindow( doc );\n\t\treturn {\n\t\t\ttop: box.top + win.pageYOffset - docElem.clientTop,\n\t\t\tleft: box.left + win.pageXOffset - docElem.clientLeft\n\t\t};\n\t},\n\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset,\n\t\t\telem = this[ 0 ],\n\t\t\tparentOffset = { top: 0, left: 0 };\n\n\t\t// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\t\t\t// Assume getBoundingClientRect is there when computed position is fixed\n\t\t\toffset = elem.getBoundingClientRect();\n\n\t\t} else {\n\t\t\t// Get *real* offsetParent\n\t\t\toffsetParent = this.offsetParent();\n\n\t\t\t// Get correct offsets\n\t\t\toffset = this.offset();\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\n\t\t\t\tparentOffset = offsetParent.offset();\n\t\t\t}\n\n\t\t\t// Add offsetParent borders\n\t\t\tparentOffset.top += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true )\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || docElem;\n\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\" ) === \"static\" ) ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent || docElem;\n\t\t});\n\t}\n});\n\n// Create scrollLeft and scrollTop methods\njQuery.each( { scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function( method, prop ) {\n\tvar top = \"pageYOffset\" === prop;\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? win[ prop ] : elem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : window.pageXOffset,\n\t\t\t\t\ttop ? val : window.pageYOffset\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\n// Support: Safari<7+, Chrome<37+\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280\n// getComputedStyle returns percent when specified for top/left/bottom/right;\n// rather than make the css module depend on the offset module, just check for it here\njQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\tjQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,\n\t\tfunction( elem, computed ) {\n\t\t\tif ( computed ) {\n\t\t\t\tcomputed = curCSS( elem, prop );\n\t\t\t\t// If curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n});\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// Margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n\n\n// The number of elements contained in the matched element set\njQuery.fn.size = function() {\n\treturn this.length;\n};\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\n\n// Note that for maximum portability, libraries that are not jQuery should\n// declare themselves as anonymous modules, and avoid setting a global if an\n// AMD loader is present. jQuery is a special case. For more information, see\n// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon\n\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( \"jquery\", [], function() {\n\t\treturn jQuery;\n\t});\n}\n\n\n\n\nvar\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$;\n\njQuery.noConflict = function( deep ) {\n\tif ( window.$ === jQuery ) {\n\t\twindow.$ = _$;\n\t}\n\n\tif ( deep && window.jQuery === jQuery ) {\n\t\twindow.jQuery = _jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in AMD\n// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\nif ( typeof noGlobal === strundefined ) {\n\twindow.jQuery = window.$ = jQuery;\n}\n\n\n\n\nreturn jQuery;\n\n}));\n"
  },
  {
    "path": "js/jquery.nav.js",
    "content": "/*\n * jQuery One Page Nav Plugin\n * http://github.com/davist11/jQuery-One-Page-Nav\n *\n * Copyright (c) 2010 Trevor Davis (http://trevordavis.net)\n * Dual licensed under the MIT and GPL licenses.\n * Uses the same license as jQuery, see:\n * http://jquery.org/license\n *\n * @version 3.0.0\n *\n * Example usage:\n * $('#nav').onePageNav({\n *   currentClass: 'current',\n *   changeHash: false,\n *   scrollSpeed: 750\n * });\n */\n\n;(function($, window, document, undefined){\n\n\t// our plugin constructor\n\tvar OnePageNav = function(elem, options){\n\t\tthis.elem = elem;\n\t\tthis.$elem = $(elem);\n\t\tthis.options = options;\n\t\tthis.metadata = this.$elem.data('plugin-options');\n\t\tthis.$win = $(window);\n\t\tthis.sections = {};\n\t\tthis.didScroll = false;\n\t\tthis.$doc = $(document);\n\t\tthis.docHeight = this.$doc.height();\n\t};\n\n\t// the plugin prototype\n\tOnePageNav.prototype = {\n\t\tdefaults: {\n\t\t\tnavItems: 'a',\n\t\t\tcurrentClass: 'current',\n\t\t\tchangeHash: false,\n\t\t\teasing: 'swing',\n\t\t\tfilter: '',\n\t\t\tscrollSpeed: 750,\n\t\t\tscrollThreshold: 0.5,\n\t\t\tbegin: false,\n\t\t\tend: false,\n\t\t\tscrollChange: false,\n\t\t\tpadding: 0\n\t\t},\n\n\t\tinit: function() {\n\t\t\t// Introduce defaults that can be extended either\n\t\t\t// globally or using an object literal.\n\t\t\tthis.config = $.extend({}, this.defaults, this.options, this.metadata);\n\n\t\t\tthis.$nav = this.$elem.find(this.config.navItems);\n\n\t\t\t//Filter any links out of the nav\n\t\t\tif(this.config.filter !== '') {\n\t\t\t\tthis.$nav = this.$nav.filter(this.config.filter);\n\t\t\t}\n\n\t\t\t//Handle clicks on the nav\n\t\t\tthis.$nav.on('click.onePageNav', $.proxy(this.handleClick, this));\n\n\t\t\t//Get the section positions\n\t\t\tthis.getPositions();\n\n\t\t\t//Handle scroll changes\n\t\t\tthis.bindInterval();\n\n\t\t\t//Update the positions on resize too\n\t\t\tthis.$win.on('resize.onePageNav', $.proxy(this.getPositions, this));\n\n\t\t\treturn this;\n\t\t},\n\n\t\tadjustNav: function(self, $parent) {\n\t\t\tself.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass);\n\t\t\t$parent.addClass(self.config.currentClass);\n\t\t},\n\n\t\tbindInterval: function() {\n\t\t\tvar self = this;\n\t\t\tvar docHeight;\n\n\t\t\tself.$win.on('scroll.onePageNav', function() {\n\t\t\t\tself.didScroll = true;\n\t\t\t});\n\n\t\t\tself.t = setInterval(function() {\n\t\t\t\tdocHeight = self.$doc.height();\n\n\t\t\t\t//If it was scrolled\n\t\t\t\tif(self.didScroll) {\n\t\t\t\t\tself.didScroll = false;\n\t\t\t\t\tself.scrollChange();\n\t\t\t\t}\n\n\t\t\t\t//If the document height changes\n\t\t\t\tif(docHeight !== self.docHeight) {\n\t\t\t\t\tself.docHeight = docHeight;\n\t\t\t\t\tself.getPositions();\n\t\t\t\t}\n\t\t\t}, 250);\n\t\t},\n\n\t\tgetHash: function($link) {\n\t\t\treturn $link.attr('href').split('#')[1];\n\t\t},\n\n\t\tgetPositions: function() {\n\t\t\tvar self = this;\n\t\t\tvar linkHref;\n\t\t\tvar topPos;\n\t\t\tvar $target;\n\n\t\t\tself.$nav.each(function() {\n\t\t\t\tlinkHref = self.getHash($(this));\n\t\t\t\t$target = $('#' + linkHref);\n\n\t\t\t\tif($target.length) {\n\t\t\t\t\ttopPos = $target.offset().top;\n\t\t\t\t\tself.sections[linkHref] = Math.round(topPos);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tgetSection: function(windowPos) {\n\t\t\tvar returnValue = null;\n\t\t\tvar windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold);\n\n\t\t\tfor(var section in this.sections) {\n\t\t\t\tif((this.sections[section] - windowHeight) < windowPos) {\n\t\t\t\t\treturnValue = section;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn returnValue;\n\t\t},\n\n\t\thandleClick: function(e) {\n\t\t\tvar self = this;\n\t\t\tvar $link = $(e.currentTarget);\n\t\t\tvar $parent = $link.parent();\n\t\t\tvar newLoc = '#' + self.getHash($link);\n\n\t\t\tif(!$parent.hasClass(self.config.currentClass)) {\n\t\t\t\t//Start callback\n\t\t\t\tif(self.config.begin) {\n\t\t\t\t\tself.config.begin();\n\t\t\t\t}\n\n\t\t\t\t//Change the highlighted nav item\n\t\t\t\tself.adjustNav(self, $parent);\n\n\t\t\t\t//Removing the auto-adjust on scroll\n\t\t\t\tself.unbindInterval();\n\n\t\t\t\t//Scroll to the correct position\n\t\t\t\tself.scrollTo(newLoc, function() {\n\t\t\t\t\t//Do we need to change the hash?\n\t\t\t\t\tif(self.config.changeHash) {\n\t\t\t\t\t\twindow.location.hash = newLoc;\n\t\t\t\t\t}\n\n\t\t\t\t\t//Add the auto-adjust on scroll back in\n\t\t\t\t\tself.bindInterval();\n\n\t\t\t\t\t//End callback\n\t\t\t\t\tif(self.config.end) {\n\t\t\t\t\t\tself.config.end();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\te.preventDefault();\n\t\t},\n\n\t\tscrollChange: function() {\n\t\t\tvar windowTop = this.$win.scrollTop();\n\t\t\tvar position = this.getSection(windowTop);\n\t\t\tvar $parent;\n\n\t\t\t//If the position is set\n\t\t\tif(position !== null) {\n\t\t\t\t$parent = this.$elem.find('a[href$=\"#' + position + '\"]').parent();\n\n\t\t\t\t//If it's not already the current section\n\t\t\t\tif(!$parent.hasClass(this.config.currentClass)) {\n\t\t\t\t\t//Change the highlighted nav item\n\t\t\t\t\tthis.adjustNav(this, $parent);\n\n\t\t\t\t\t//If there is a scrollChange callback\n\t\t\t\t\tif(this.config.scrollChange) {\n\t\t\t\t\t\tthis.config.scrollChange($parent);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tscrollTo: function(target, callback) {\n\t\t\tvar offset = $(target).offset().top - this.config.padding;\n\n\t\t\t$('html, body').animate({\n\t\t\t\tscrollTop: offset\n\t\t\t}, this.config.scrollSpeed, this.config.easing, callback);\n\t\t},\n\n\t\tunbindInterval: function() {\n\t\t\tclearInterval(this.t);\n\t\t\tthis.$win.unbind('scroll.onePageNav');\n\t\t}\n\t};\n\n\tOnePageNav.defaults = OnePageNav.prototype.defaults;\n\n\t$.fn.onePageNav = function(options) {\n\t\treturn this.each(function() {\n\t\t\tnew OnePageNav(this, options).init();\n\t\t});\n\t};\n\n})( jQuery, window , document );"
  },
  {
    "path": "js/jquery.tagcloud.js",
    "content": "(function($) {\n\n  $.fn.tagcloud = function(options) {\n    var opts = $.extend({}, $.fn.tagcloud.defaults, options);\n    tagWeights = this.map(function(){\n      return $(this).attr(\"rel\");\n    });\n    tagWeights = jQuery.makeArray(tagWeights).sort(compareWeights);\n    lowest = tagWeights[0];\n    highest = tagWeights.pop();\n    range = highest - lowest;\n    if(range === 0) {range = 1;}\n    // Sizes\n    if (opts.size) {\n      fontIncr = (opts.size.end - opts.size.start)/range;\n    }\n    // Colors\n    if (opts.color) {\n      colorIncr = colorIncrement (opts.color, range);\n    }\n    return this.each(function() {\n      weighting = $(this).attr(\"rel\") - lowest;\n      if (opts.size) {\n        $(this).css({\"font-size\": opts.size.start + (weighting * fontIncr) + opts.size.unit});\n      }\n      if (opts.color) {\n        // change color to background-color\n        $(this).css({\"backgroundColor\": tagColor(opts.color, colorIncr, weighting)});\n      }\n    });\n  };\n\n  $.fn.tagcloud.defaults = {\n    size: {start: 14, end: 18, unit: \"pt\"}\n  };\n\n  // Converts hex to an RGB array\n  function toRGB (code) {\n    if (code.length == 4) {\n      code = jQuery.map(/\\w+/.exec(code), function(el) {return el + el; }).join(\"\");\n    }\n    hex = /(\\w{2})(\\w{2})(\\w{2})/.exec(code);\n    return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];\n  }\n\n  // Converts an RGB array to hex\n  function toHex (ary) {\n    return \"#\" + jQuery.map(ary, function(i) {\n      hex =  i.toString(16);\n      hex = (hex.length == 1) ? \"0\" + hex : hex;\n      return hex;\n    }).join(\"\");\n  }\n\n  function colorIncrement (color, range) {\n    return jQuery.map(toRGB(color.end), function(n, i) {\n      return (n - toRGB(color.start)[i])/range;\n    });\n  }\n\n  function tagColor (color, increment, weighting) {\n    rgb = jQuery.map(toRGB(color.start), function(n, i) {\n      ref = Math.round(n + (increment[i] * weighting));\n      if (ref > 255) {\n        ref = 255;\n      } else {\n        if (ref < 0) {\n          ref = 0;\n        }\n      }\n      return ref;\n    });\n    return toHex(rgb);\n  }\n\n  function compareWeights(a, b)\n  {\n    return a - b;\n  }\n\n})(jQuery);\n"
  },
  {
    "path": "less/hux-blog.less",
    "content": "@import \"variables.less\";\n@import \"mixins.less\";\n@import \"sidebar.less\";\n@import \"side-catalog.less\";\n\n// Global Components\n\nbody {\n\t.sans-serif;\n\tfont-size: 16px;\n\t// Hux mpdify to 16px (Mobile First), and increase to 20px while 768+ width\n\tcolor: @gray-dark;\n\t//-webkit-user-select:text; //对于 Blog 还是先不开启这句。\n\toverflow-x : hidden;\n}\n\n// -- Typography\n\np {\n\tmargin: 30px 0;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n\t.sans-serif;\n\tline-height: 1.1;\n\tfont-weight: bold;\n}\nh1{\n\t// drop V1.1.1\n\t//font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n}\nh4{\n    font-size: 21px;\n}\na {\n\tcolor: @gray-dark;\n\t&:hover,\n\t&:focus {\n\t\tcolor: @brand-primary;\n\t}\n}\n\na img {\n\t&:hover,\n\t&:focus {\n\t\tcursor: zoom-in;\n\t}\n}\n\narticle{\n\toverflow-x : hidden;\n}\nblockquote {\n\tcolor: @gray;\n\tfont-style: italic;\n\tfont-size: 0.95em;\n\tmargin: 20px 0 20px;\n\tp{\n\t\tmargin: 0;\n\t}\n}\n\n// Utils Style Class can be used in Markdown.\nsmall.img-hint{\n\tdisplay: block;\n\tmargin-top: -20px;\n\ttext-align: center;\n}\nbr + small.img-hint{\n\tmargin-top: -40px;\n}\nimg.shadow{\n\tbox-shadow: rgba(0, 0, 0, 0.258824) 0px 2px 5px 0px;\n}\n// Utils Style End\n\n// Select \nselect{\n\t-webkit-appearance:none;\n\tmargin-top:15px;\n\tcolor:#337ab7;\n\tborder-color:#337ab7;\n\tpadding: 0em 0.4em;\n\tbackground: white;\n\t&.sel-lang{\n\t\tmin-height: 28px;\n\t\tfont-size:14px;\n\t}\n}\n\n\n// override table style in bootstrap\n.table th, .table td{\n\tborder: 1px solid #eee !important;\n}\n\nhr.small {\n\tmax-width: 100px;\n\tmargin: 15px auto;\n\tborder-width: 4px;\n\tborder-color: white;\n}\n// add by Hux\npre,.table-responsive{\n\t// sometimes you should use vendor-feature.\n\t-webkit-overflow-scrolling: touch;\n}\npre code{\n\tdisplay: block;\n\twidth: auto;\n\twhite-space: pre;\t// save it but no-wrap;\n\tword-wrap: normal;\t// no-wrap\n}\n\n// In the list of posts\n.postlist-container{\n\tmargin-bottom: 15px;\n}\n\n// In the post.\n.post-container{\n\ta{\n\t\tcolor:#337ab7;\n\t\t// different to @brand-primary\n\t\t&:hover,\n\t\t&:focus {\n\t\t\tcolor: @brand-primary;\n\t\t}\n\t}\n\th1,\n\th2,\n\th3,\n\th4,\n\th5,\n\th6 {\n\t\tmargin: 30px 0 10px;\n\t}\n\th5{\n\t\tfont-size: 19px;\n\t\tfont-weight: 600;\n\t\tcolor:gray;\n\t\t& + p{\n\t\t\tmargin-top: 5px;\n\t\t}\n\t}\n\th6{\n\t\tfont-size: 16px;\n\t\tfont-weight: 600;\n\t\tcolor: gray;\n\t\t& + p{\n\t\t\tmargin-top: 5px;\n\t\t}\n\t}\n\tul,ol{\n\t\tmargin-bottom: 40px;\n\t\t@media screen and (max-width: 768px){\n\t\t\t&{\n\t\t\t\tpadding-left: 30px;\n\t\t\t}\n\t\t}\n\t\t@media screen and (max-width: 500px){\n\t\t\t&{\n\t\t\t\tpadding-left: 20px;\n\t\t\t}\n\t\t}\n\t}\n\tol ol, ol ul, ul ol, ul ul{\n\t\tmargin-bottom: 5px\n\t}\n\tli{\n\t\tp{\n\t\t\tmargin: 0;\n\t\t\tmargin-bottom: 5px;\n\t\t}\n\t\th1,h2,h3,h4,h5,h6{\n\t\t\tline-height: 2;\n\t\t\tmargin-top: 20px;\n\t\t}\n\t}\n\t// V1.6 Hux display title by default.\n\t.pager li{\n\t\twidth: 48%;\n\t\t&.next{ float: right};\n\t\t&.previous{ float: left};\n\t\t> a {\n\t\t\twidth: 100%;\n\t\t\t> span{\n\t\t\t\tcolor: @gray;\n\t\t\t\tfont-weight: normal;\n\t\t\t\tletter-spacing: 0.5px;\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n\n// Navigation\n\n// materialize, mobile only\n@media only screen and (max-width: 767px) {\n\t/**\n\t * Layout\n\t * Since V1.6 we use absolute positioning to prevent to expand container-fluid\n\t * which would cover tags. A absolute positioning make a new layer.\n\t */\n\t.navbar-default .navbar-collapse{\n\t\tposition: absolute; \n     \tright: 0;\n\t\tborder: none;\n\t\tbackground: white;\n\t\tbox-shadow: 0px 5px 10px 2px rgba(0, 0, 0, 0.2);\n\t\tbox-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.239216) 0px 1px 4px;\n\t\tborder-radius: 2px;\n\t\twidth: 170px;\n\t}\n\t/**\n\t * Animation\n\t * HuxBlog-Navbar using genuine Material Design Animation\n\t */\n\t#huxblog_navbar{ \t\n\t\t/**\n\t\t * Sharable code and 'out' function\n\t\t */\n\t\t// the container do width-transition\n\t\topacity: 0;\n\t\ttransform: scaleX(0);\n\t\ttransform-origin: top right;\n\t\ttransition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n\t\t-webkit-transform: scaleX(0);\n\t\t-webkit-transform-origin: top right;\n\t\t-webkit-transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n\t\ta {\n\t\t\tfont-size: 13px;\n\t\t\tline-height: 28px;\n\t\t}\n\t\t.navbar-collapse{\t// the navbar do height-transition\n\t\t\theight: 0px; \t// to solve container-mask-tags issue (default state).\n\t\t\ttransform: scaleY(0);\n\t\t\ttransform-origin: top right;\n\t\t\ttransition: transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n\t\t\t-webkit-transform: scaleY(0);\n\t\t\t-webkit-transform-origin: top right;\n\t\t\t-webkit-transition: -webkit-transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n\t\t}\n\t\tli{\n\t\t\topacity: 0;\n\t\t\ttransition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n\t\t\t-webkit-transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n\t\t}\n\t\t/**\n\t\t *'In' Animation\n\t\t */\n\t\t&.in {\n\t\t\ttransform: scaleX(1);\n\t\t\t-webkit-transform: scaleX(1);\n\t\t\topacity: 1;\n\t\t\ttransition: all 250ms cubic-bezier(0.23,1,0.32,1) 0ms;\n\t\t\t-webkit-transition: all 250ms cubic-bezier(0.23,1,0.32,1) 0ms;\n\t\t\t.navbar-collapse{\n\t\t\t\ttransform: scaleY(1);\n\t\t\t\t-webkit-transform: scaleY(1);\n\t\t\t\ttransition: transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n\t\t\t\t-webkit-transition: -webkit-transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n\t\t\t}\n\t\t\tli{\n\t\t\t\topacity: 1;\n\t\t\t\ttransition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n\t\t\t\t-webkit-transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.navbar-custom {\n\t// materialize\n\tbackground: none;\n\tborder:none;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\tz-index: 3;\n\t.sans-serif;\n\t.navbar-brand {\n\t\tfont-weight: 800;\n\t\t// materialize\n\t\tcolor: white;\n\t\theight: 56px;\n\t\tline-height: 25px;\n\t\t&:hover{\n\t\t\tcolor: rgba(255, 255, 255, 0.8);\n\t\t}\n\t}\n\t.nav {\n\t\tli {\n\t\t\ta {\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tfont-size: 12px;\n\t\t\t\tline-height: 20px;\n\t\t\t\tfont-weight: 800;\n\t\t\t\tletter-spacing: 1px;\n\t\t\t\t// only highlight in mobile\n\t\t\t\t&:active{\n\t\t\t\t\tbackground: rgba(0,0,0,0.12)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t@media only screen and (min-width: 768px) {\n\t\tbody{\n\t\t\tfont-size: 20px;\n\t\t}\n\t\tbackground: transparent;\n\t\tborder-bottom: 1px solid transparent;\n\t\t.navbar-brand {\n\t\t\tcolor: white;\n\t\t\tpadding: 20px;\n\t\t\tline-height: 20px;\n\t\t\t&:hover,\n\t\t\t&:focus {\n\t\t\t\tcolor: @white-faded;\n\t\t\t}\n\t\t}\n\t\t.nav {\n\t\t\tli {\n\t\t\t\ta {\n\t\t\t\t\tcolor: white;\n\t\t\t\t\tpadding: 20px;\n\t\t\t\t\t&:hover,\n\t\t\t\t\t&:focus {\n\t\t\t\t\t\tcolor: @white-faded;\n\t\t\t\t\t}\n\t\t\t\t\t&:active{\n\t\t\t\t\t\tbackground: none;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t@media only screen and (min-width: 1170px) {\n\t\t-webkit-transition: background-color 0.3s;\n\t\t-moz-transition: background-color 0.3s;\n\t\ttransition: background-color 0.3s;\n\t\t/* Force Hardware Acceleration in WebKit */\n\t\t-webkit-transform: translate3d(0, 0, 0);\n\t\t-moz-transform: translate3d(0, 0, 0);\n\t\t-ms-transform: translate3d(0, 0, 0);\n\t\t-o-transform: translate3d(0, 0, 0);\n\t\ttransform: translate3d(0, 0, 0);\n\t\t-webkit-backface-visibility: hidden;\n\t\tbackface-visibility: hidden;\n\t\t&.is-fixed {\n\t\t\t/* when the user scrolls down, we hide the header right above the viewport */\n\t\t\tposition: fixed;\n\t\t\ttop: -61px;\n\t\t\tbackground-color: fade(white, 90%);\n\t\t\tborder-bottom: 1px solid darken(white, 5%);\n\t\t\t-webkit-transition: -webkit-transform 0.3s;\n\t\t\t-moz-transition: -moz-transform 0.3s;\n\t\t\ttransition: transform 0.3s;\n\t\t\t.navbar-brand {\n\t\t\t\tcolor: @gray-dark;\n\t\t\t\t&:hover,\n\t\t\t\t&:focus {\n\t\t\t\t\tcolor: @brand-primary;\n\t\t\t\t}\n\t\t\t}\n\t\t\t.nav {\n\t\t\t\tli {\n\t\t\t\t\ta {\n\t\t\t\t\t\tcolor: @gray-dark;\n\t\t\t\t\t\t&:hover,\n\t\t\t\t\t\t&:focus {\n\t\t\t\t\t\t\tcolor: @brand-primary;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t&.is-visible {\n\t\t\t/* if the user changes the scrolling direction, we show the header */\n\t\t\t-webkit-transform: translate3d(0, 100%, 0);\n\t\t\t-moz-transform: translate3d(0, 100%, 0);\n\t\t\t-ms-transform: translate3d(0, 100%, 0);\n\t\t\t-o-transform: translate3d(0, 100%, 0);\n\t\t\ttransform: translate3d(0, 100%, 0);\n\t\t}\n\t}\n}\n\n// Header\n\n.intro-header {\n\tbackground: no-repeat center center;\n\tbackground-color: @gray;\n\tbackground-attachment: scroll;\n\t.background-cover;\n\t// NOTE: Background images are set within the HTML using inline CSS!\n\tmargin-bottom: 0px;  /* 0 on mobile, modify by Hux */\n\t@media only screen and (min-width: 768px) {\n\t\tmargin-bottom: 20px;  /* response on desktop */\n\t}\n\t.site-heading,\n\t.post-heading,\n\t.page-heading {\n\t\tpadding: 85px 0 55px;\n\t\tcolor: white;\n\t\t@media only screen and (min-width: 768px) {\n\t\t\tpadding: 150px 0;\n\t\t}\n\t}\n\t// masterialize\n\t.site-heading{\n\t\tpadding: 95px 0 70px;\n\t\t@media only screen and (min-width: 768px) {\n\t\t\tpadding: 150px 0;\n\t\t}\n\t}\n\t.site-heading,\n\t.page-heading {\n\t\ttext-align: center;\n\t\th1 {\n\t\t\tmargin-top: 0;\n\t\t\tfont-size: 50px;\n\t\t}\n\t\t.subheading {\n\t\t\t.sans-serif;\n\t\t\tfont-size: 18px;\n\t\t\tline-height: 1.1;\n\t\t\tdisplay: block;\n\t\t\tfont-weight: 300;\n\t\t\tmargin: 10px 0 0;\n\t\t}\n\t\t@media only screen and (min-width: 768px) {\n\t\t\th1 {\n\t\t\t\tfont-size: 80px;\n\t\t\t}\n\t\t}\n\t}\n\t.post-heading {\n\t\th1 {\n\t\t\tfont-size: 30px;\n\t\t\tmargin-bottom: 24px;\n\t\t}\n\t\t.subheading,\n\t\t.meta {\n\t\t\tline-height: 1.1;\n\t\t\tdisplay: block;\n\t\t}\n\t\t.subheading {\n\t\t\t.sans-serif;\n\t\t\tfont-size: 17px;\n\t\t\tline-height: 1.4;\n\t\t\tfont-weight: normal;\n\t\t\tmargin: 10px 0 30px;\n\t\t\tmargin-top: -5px;\n\t\t}\n\t\t.meta {\n\t\t\t.serif;\n\t\t\tfont-style: italic;\n\t\t\tfont-weight: 300;\n\t\t\tfont-size: 18px;\n\t\t\ta {\n\t\t\t\tcolor: white;\n\t\t\t}\n\t\t}\n\t\t@media only screen and (min-width: 768px) {\n\t\t\th1 {\n\t\t\t\tfont-size: 55px;\n\t\t\t}\n\t\t\t.subheading {\n\t\t\t\tfont-size: 30px;\n\t\t\t}\n\t\t\t.meta {\n\t\t\t\tfont-size: 20px;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Post Preview Pages (Home Page)\n\n.post-preview {\n\t> a {\n\t\tcolor: @gray-dark;\n\t\t&:hover,\n\t\t&:focus {\n\t\t\ttext-decoration: none;\n\t\t\tcolor: @brand-primary;\n\t\t}\n\t\t> .post-title {\n\t\t\tfont-size: 21px;\n\t\t\tline-height: 1.3;\n\t\t\tmargin-top: 30px;\n\t\t\tmargin-bottom: 8px;\n\t\t}\n\t\t> .post-subtitle {\n\t\t\tfont-size: 15px;\n\t\t\tline-height: 1.3;\n\t\t\tmargin: 0;\n\t\t\tfont-weight: 300;\n\t\t\tmargin-bottom: 10px;\n\t\t}\n\t}\n\t> .post-meta {\n\t\t.serif;\n\t\tcolor: @gray;\n\t\tfont-size: 16px;\n\t\tfont-style: italic;\n\t\tmargin-top: 0;\n\t\t> a {\n\t\t\ttext-decoration: none;\n\t\t\tcolor: @gray-dark;\n\t\t\t&:hover,\n\t\t\t&:focus {\n\t\t\t\tcolor: @brand-primary;\n\t\t\t\ttext-decoration: underline;\n\t\t\t}\n\t\t}\n\t}\n\t@media only screen and (min-width: 768px) {\n\t\t> a {\n\t\t\t> .post-title {\n\t\t\t\tfont-size: 26px;\n\t\t\t\tline-height: 1.3;\n\t\t\t\tmargin-bottom: 10px;\n\t\t\t}\n\t\t\t> .post-subtitle{\n\t\t\t\tfont-size: 16px;\n\t\t\t}\n\t\t}\n\t\t.post-meta{\n\t\t\tfont-size: 18px;\n\t\t}\n\t}\n}\n\n// Hux add home-page post-content-preview\n.post-content-preview{\n    font-size: 13px;\n    font-style: italic;\n    color:#a3a3a3;\n    &:hover{\n        color: @brand-primary;\n    }\n    @media only screen and (min-width: 768px) {\n        font-size:14px;\n    }\n}\n\n// Sections\n\n.section-heading {\n\tfont-size: 36px;\n\tmargin-top: 60px;\n\tfont-weight: 700;\n}\n\n.caption {\n\ttext-align: center;\n\tfont-size: 14px;\n\tpadding: 10px;\n\tfont-style: italic;\n\tmargin: 0;\n\tdisplay: block;\n\tborder-bottom-right-radius: 5px;\n\tborder-bottom-left-radius: 5px;\n}\n\nfooter {\n\tfont-size: 20px;\n\tpadding: 50px 0 65px;\n\t.list-inline {\n\t\tmargin: 0;\n\t\tpadding: 0;\n\t}\n\t.copyright {\n\t\tfont-size: 14px;\n\t\ttext-align: center;\n\t\tmargin-bottom: 0;\n\t\ta{\n\t\t\tcolor:#337ab7;\n\t\t\t// different to @brand-primary\n\t\t\t&:hover,\n\t\t\t&:focus {\n\t\t\t\tcolor: @brand-primary;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Contact Form Styles\n\n.floating-label-form-group {\n\tfont-size: 14px;\n\tposition: relative;\n\tmargin-bottom: 0;\n\tpadding-bottom: 0.5em;\n\tborder-bottom: 1px solid @gray-light;\n\tinput,\n\ttextarea {\n\t\tz-index: 1;\n\t\tposition: relative;\n\t\tpadding-right: 0;\n\t\tpadding-left: 0;\n\t\tborder: none;\n\t\tborder-radius: 0;\n\t\tfont-size: 1.5em;\n\t\tbackground: none;\n\t\tbox-shadow: none !important;\n\t\tresize: none;\n\t}\n\tlabel {\n\t\tdisplay: block;\n\t\tz-index: 0;\n\t\tposition: relative;\n\t\ttop: 2em;\n\t\tmargin: 0;\n\t\tfont-size: 0.85em;\n\t\tline-height: 1.764705882em;\n\t\tvertical-align: middle;\n\t\tvertical-align: baseline;\n\t\topacity: 0;\n\t\t-webkit-transition: top 0.3s ease,opacity 0.3s ease;\n\t\t-moz-transition: top 0.3s ease,opacity 0.3s ease;\n\t\t-ms-transition: top 0.3s ease,opacity 0.3s ease;\n\t\ttransition: top 0.3s ease,opacity 0.3s ease;\n\t}\n\t&::not(:first-child) {\n\t\tpadding-left: 14px;\n\t\tborder-left: 1px solid @gray-light;\n\t}\n}\n\n.floating-label-form-group-with-value {\n\tlabel {\n\t\ttop: 0;\n\t\topacity: 1;\n\t}\n}\n\n.floating-label-form-group-with-focus {\n\tlabel {\n\t\tcolor: @brand-primary;\n\t}\n}\n\nform .row:first-child .floating-label-form-group {\n\tborder-top: 1px solid @gray-light;\n}\n\n// Button Styles\n\n.btn {\n\t.sans-serif;\n\ttext-transform: uppercase;\n\tfont-size: 14px;\n\tfont-weight: 800;\n\tletter-spacing: 1px;\n\tborder-radius: 0;\n\tpadding: 15px 25px;\n}\n\n.btn-lg {\n\tfont-size: 16px;\n\tpadding: 25px 35px;\n}\n\n.btn-default {\n\t&:hover,\n\t&:focus {\n\t\tbackground-color: @brand-primary;\n\t\tborder: 1px solid @brand-primary;\n\t\tcolor: white;\n\t}\n}\n\n// Pager Styling\n\n.pager {\n\tmargin: 20px 0 0 !important;\n\tpadding:0px !important;\n\n\tli {\n\t\t> a,\n\t\t> span {\n\t\t\t.sans-serif;\n\t\t\ttext-transform: uppercase;\n\t\t\tfont-size: 13px;\n\t\t\tfont-weight: 800;\n\t\t\tletter-spacing: 1px;\n\t\t\tpadding: 10px;\n\t\t\tbackground-color: white;\n\t\t\tborder-radius: 0;\n\t\t\t@media only screen and (min-width: 768px) {\n\t\t\t\tfont-size: 14px;\n\t\t\t\tpadding: 15px 25px;\n\t\t\t}\n\t\t}\n\n\t\t> a {\n\t\t\tcolor: @gray-dark;\n\t\t}\n\t\t> a:hover,\n\t\t> a:focus {\n\t\t\tcolor: white;\n\t\t\tbackground-color: @brand-primary;\n\t\t\tborder: 1px solid @brand-primary;\n\n\t\t\t// V1.6 display title\n\t\t\t> span{\n\t\t\t\tcolor: white;\n\t\t\t}\n\t\t}\n\t}\n\n\t.disabled {\n\t\t> a,\n\t\t> a:hover,\n\t\t> a:focus,\n\t\t> span {\n\t\t\tcolor: @gray;\n\t\t\tbackground-color: @gray-dark;\n\t\t\tcursor: not-allowed;\n\t\t}\n\t}\n}\n\n// -- Highlight Color Customization\n\n::-moz-selection {\n\tcolor: white;\n\ttext-shadow: none;\n\tbackground: @brand-primary;\n}\n\n::selection {\n\tcolor: white;\n\ttext-shadow: none;\n\tbackground: @brand-primary;\n}\n\nimg::selection {\n\tcolor: white;\n\tbackground: transparent;\n}\n\nimg::-moz-selection {\n\tcolor: white;\n\tbackground: transparent;\n}\n\n\n/* Hux add tags support */\n.tags{\n\tmargin-bottom: -5px;\n\ta,.tag{\n\t\tdisplay: inline-block;\n\t\tborder: 1px solid rgba(255,255,255,0.8);\n\t\tborder-radius: 999em;\n\t\tpadding: 0 10px;\n\t\tcolor: rgba(255,255,255,1);\n\t\tline-height: 24px;\n\t\tfont-size: 12px;\n\t\ttext-decoration: none;\n\t\tmargin: 0 1px;\n\t\tmargin-bottom: 6px;\n\t\t&:hover, &:active{\n\t\t\tcolor:white;\n\t\t\tborder-color: white;\n\t\t\tbackground-color: rgba(255,255,255,0.4);\n\t\t\ttext-decoration: none;\n\n\t\t}\n\t\t@media only screen and (min-width: 768px) {\n\t\t\tmargin-right: 5px;\n\t\t}\n\t}\n}\n\n#tag-heading{\n\tpadding: 70px 0 60px;\n\t@media only screen and (min-width: 768px) {\n\t\tpadding:55px 0;\n\t}\n\n}\n// tag_cloud\n#tag_cloud{\n\tmargin:20px 0 15px 0;\n\ta,.tag{\n\t\tfont-size: 14px;\n\t\tborder: none;\n\t\tline-height: 28px;\n\t\tmargin: 0 2px;\n\t\tmargin-bottom: 8px;\n\t\tbackground: #D6D6D6;\n\t\t&:hover, &:active{\n\t\t\tbackground-color: #0085a1 !important;\n\t\t}\n\t}\n\t@media only screen and (min-width: 768px) {\n\t\tmargin-bottom: 25px;\n\t}\n}\n\n.tag-comments{\n    font-size: 12px;\n    \t@media only screen and (min-width: 768px) {\n\t\tfont-size:14px;\n\t}\n}\n\n.t{\n\t//margin-top: 50px;\n\t&:first-child{\n\t\tmargin-top: 0px;\n\t}\n\thr:last-of-type{\n\t\t//display: none;\n\t}\n}\n.listing-seperator{\n\tcolor:#0085a1;\n\tfont-size: 21px !important;\n\t&::before{\n\t\tmargin-right: 5px;\n\t}\n\t@media only screen and (min-width: 768px) {\n\t\tfont-size: 20px !important;\n\t\tline-height: 2 !important;\n\t}\n}\n\n// Customize Style for Posts in One-Tag-List\n.one-tag-list{\n\t.tag-text{\n\t\tfont-weight: 200;\n\t\t.sans-serif;\n\t}\n\t.post-preview {\n\t\tposition: relative;\n\t\t> a {\n\t\t\t.post-title{\n\t\t\t\tfont-size: 16px;\n\t\t\t\tfont-weight: 500;\n\t\t\t\tmargin-top: 20px;\n\t\t\t}\n\t\t\t.post-subtitle{\n\t\t\t\tfont-size: 12px;\n\t\t\t}\n\t\t}\n\t\t> .post-meta{\n\t\t\tposition: absolute;\n\t\t\tright: 5px;\n\t\t\tbottom: 0px;\n\t\t\tmargin: 0px;\n\t\t\tfont-size: 12px;\n\t\t\tline-height: 12px;\n\t\t}\n\t\t@media only screen and (min-width: 768px) {\n\t\t\tmargin-left: 20px;\n\t\t\t> a {\n\t\t\t\t> .post-title {\n\t\t\t\t\tfont-size: 18px;\n\t\t\t\t\tline-height: 1.3\n\t\t\t\t}\n\t\t\t\t> .post-subtitle{\n\t\t\t\t\tfont-size: 14px;\n\t\t\t\t}\n\t\t\t}\n\t\t\t.post-meta{\n\t\t\t\tfont-size: 18px;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* Tags support End*/\n\n/* Hux make all img responsible in post-container */\n.post-container img{\n\tdisplay: block;\n\tmax-width: 100%;\n\theight: auto;\n\tmargin: 1.5em auto 1.6em auto;\n}\n\n\n/* Hux Optimize UserExperience */\n.navbar-default .navbar-toggle {\n\t&:focus, &:hover{\n\t\tbackground-color: inherit;\n\t}\n  \t&:active{\n  \t\tbackground-color: rgba(255, 255, 255, 0.25);\n  \t}\n}\n\n/* Hux customize Style for navBar button */\n\n.navbar-default .navbar-toggle{\n\tborder-color: transparent;\n\tpadding:19px 16px;\n\tmargin-top: 2px;\n\tmargin-right: 2px;\n\tmargin-bottom: 2px;\n\tborder-radius: 50%;\n\t.icon-bar{\n\t\twidth: 18px;\n\t\tborder-radius: 0px;\n\t\t// materialize\n\t\tbackground-color: white;\n\t}\n\t.icon-bar+.icon-bar{\n\t\tmargin-top: 3px;\n\t}\n}\n\n\n/* Hux customize Style for Duoshuo */\n.comment{\n\tmargin-top: 20px;\n\t#ds-thread {\n\t\t#ds-reset {\n\t\t\ta.ds-like-thread-button{\n\t\t\t\tborder:1px solid #ddd;\n\t\t\t\tborder-radius: 0px;\n\t\t\t\tbackground: white;\n\t\t\t\tbox-shadow: none;\n\t\t\t\ttext-shadow: none;\n\t\t\t}\n\t\t\ta.ds-thread-liked{\n\n\t\t\t}\n\t\t\tli.ds-tab a.ds-current{\n\t\t\t\tborder:1px solid #ddd;\n\t\t\t\tborder-radius: 0px;\n\t\t\t\tbackground: white;\n\t\t\t\tbox-shadow: none;\n\t\t\t\ttext-shadow: none;\n\t\t\t}\n\t\t\t.ds-textarea-wrapper{\n\t\t\t\t//border:1px solid #ddd;\n\t\t\t\tbackground: none;\n\t\t\t}\n\t\t\t.ds-gradient-bg{\n\t\t\t\tbackground: none;\n\t\t\t}\n\t\t\t.ds-post-options{\n\t\t\t\tborder-bottom: 1px solid #ccc;\n\t\t\t}\n\t\t\t.ds-post-button{\n\t\t\t\tborder-bottom: 1px solid #ccc;\n\t\t\t}\n\t\t\t//v1.6 flatten button\n\t\t\t.ds-post-button{\n\t\t\t\tbackground: white;\n\t\t\t\tbox-shadow: none;\n\t\t\t\t&:hover{\n\t\t\t\t\tbackground: @gray-light;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n#ds-smilies-tooltip ul.ds-smilies-tabs li a {\n\tbackground: white !important;\n}\n\n\n// Hux fullscreen mode in 404.html\n\n.page-fullscreen .intro-header{\n\tposition: fixed;\n\tleft: 0;\n\ttop: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n.page-fullscreen #tag-heading{\n\tposition: fixed;\n\tleft: 0;\n\ttop: 0;\n\tpadding-bottom: 150px;\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: -webkit-box;\n\t-webkit-box-orient: vertical;\n\t-webkit-box-pack: center;\n\t-webkit-box-align: center;\n\n\tdisplay: -webkit-flex;\n\t-webkit-align-items: center;\n\t-webkit-justify-content: center;\n\t-webkit-flex-direction: column;\n\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-direction: column;\n}\n.page-fullscreen footer{\n\tposition: absolute;\n\twidth: 100%;\n\tbottom: 0;\n\tpadding-bottom: 20px;\n\topacity: 0.6;\n\tcolor: #fff;\n}\n.page-fullscreen footer .copyright{\n\tcolor: #fff;\n}\n.page-fullscreen footer .copyright a{\n\tcolor: #fff;\n}\n.page-fullscreen footer .copyright a:hover{\n\tcolor: #ddd;\n}\n\n\n\n"
  },
  {
    "path": "less/mixins.less",
    "content": "// Mixins\n\n.transition-all() {\n    -webkit-transition: all 0.5s;\n    -moz-transition: all 0.5s;\n    transition: all 0.5s;\n}\n\n.background-cover() {\n    -webkit-background-size: cover;\n    -moz-background-size: cover;\n    background-size: cover;\n    -o-background-size: cover;\n}\n\n.serif() {\n\tfont-family: 'Lora', 'Times New Roman', serif;\n}\n\n.sans-serif () {\n\t/* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n\tfont-family:\n        // System Font            // https://www.webkit.org/blog/3709/using-the-system-font-in-web-content/\n        -apple-system,            // OSX ^10.11 & iOS ^9  San Francisco & 苹方 for Safari\n        BlinkMacSystemFont,       // OSX ^10.11 & iOS ^9  San Francisco & 苹方 for Blink \n\n        // English First\n        \"Helvetica Neue\",         // OSX\n        \"Arial\",                  // Win \"Helvetica\"\n        //\" Segoe UI\",            // Win ^8\n\n        // Chinese Fallback\n        \"PingFang SC\",            // OSX ^10.11 & iOS ^9  苹方（华康信凭黑）\n        \"Hiragino Sans GB\",       // OSX ^10.6            冬青黑体\n        \"STHeiti\",                // OSX <10.6  & iOS <9  华文黑体\n        \"Microsoft YaHei\",        // Win                  微软雅黑\n        \"Microsoft JhengHei\",     // Win                  微软正黑\n        \"Source Han Sans SC\",     // SourceHan - begin    思源黑体\n        \"Noto Sans CJK SC\",\n        \"Source Han Sans CN\",\n        \"Noto Sans SC\",\n        \"Source Han Sans TC\",\n        \"Noto Sans CJK TC\",       // SourceHan - end\n        \"WenQuanYi Micro Hei\",    // Linux                文泉驿微米黑\n        SimSun,                   // Win old              中易宋体\n        sans-serif;               // System Fallback\n\n\tline-height: 1.7;\n}\n"
  },
  {
    "path": "less/side-catalog.less",
    "content": "// Directory Section\n\n.catalog-container{\n    padding: 0px;\n}\n\n.side-catalog {\n    &.fixed{\n        position: fixed;\n        top: -21px;\n    }\n    &.fold{\n        .catalog-toggle::before{\n            content: \"+\";\n        }\n        .catalog-body{\n            display: none;\n        }\n    }\n    .catalog-toggle::before{\n        content: \"−\";\n        position: relative;\n        margin-right: 5px;\n        bottom: 1px;\n    }\n    display: block;\n    overflow: auto;\n    height: 100%;\n    padding-bottom: 40px;\n    width: 195px;\n    .catalog-body{\n        position: relative;\n        list-style: none;\n        height: auto;\n        overflow: hidden;\n        padding-left: 0px;\n        padding-right: 5px;\n        text-indent: 0;\n        li{\n            position: relative;\n            list-style: none;\n            a{\n                padding-left: 10px;\n                max-width: 180px;\n                display: inline-block;\n                vertical-align: middle;\n                height: 30px;\n                line-height: 30px;\n                overflow: hidden;\n                text-decoration: none;\n                white-space: nowrap;\n                text-overflow: ellipsis;\n            }\n        }\n        .h1_nav,.h2_nav,.h3_nav{\n            margin-left: 0;\n            font-size: 13px;\n            font-weight: bold;\n        }\n        .h4_nav,.h5_nav,.h6_nav{\n            margin-left: 10px;\n            font-size: 12px;\n            a{\n                max-width: 170px;\n            }\n        }\n        .active{\n            a{\n                color: #0085a1!important;\n            }\n            border-radius: 4px;\n            background-color: #F5F5F5;\n        }\n    }\n}\n\n@media (max-width: 1200px){\n    .side-catalog{\n        display: none;\n    }\n}"
  },
  {
    "path": "less/sidebar.less",
    "content": "@import \"variables.less\";\n\n// Sidebar Components\n\n// Large Screen\n@media (min-width: 1200px){\n    .post-container, .sidebar-container{\n        padding-right: 5%;\n    }\n}\n@media (min-width: 768px){\n    .post-container{\n        padding-right: 5%;\n    }\n}\n\n// Container of Sidebar, also Friends\n.sidebar-container{\n    color: @gray-l;\n    font-size: 14px;\n    h5{\n        color: @gray;\n        padding-bottom: 1em;\n        a{\n            color: @gray !important;\n            text-decoration: none;\n        }\n    }\n    a{\n        color: @gray-l !important;\n        &:hover, &:active{\n            color: @brand-primary !important;\n        }\n    }\n    .tags{\n        a{\n            border-color: @gray-l;\n            &:hover, &:active{\n                border-color: @brand-primary;\n            }\n        }\n    }\n    .short-about{\n        img{\n            width: 80%;\n            display: block;\n            border-radius: 5px;\n            margin-bottom: 20px;\n        }\n        p{\n            margin-top: 0px;\n            margin-bottom: 20px;\n        }\n        .list-inline>li{\n            padding-left: 0px;\n        }\n    }\n}\n"
  },
  {
    "path": "less/variables.less",
    "content": "// Variables\n\n@brand-primary: #0085A1;\n@gray-dark: lighten(black, 25%);\n@gray: lighten(black, 50%);\n@gray-l: lighten(black, 75%);\n@white-faded: fade(white, 80%);\n@gray-light: #eee;\n"
  },
  {
    "path": "offline.html",
    "content": "---\nlayout: default\ndescription: \"网络似乎出现了问题 😥\"\nheader-img: \"img/404-bg.jpg\"\npermalink: /offline.html\n---\n\n\n<!-- Page Header -->\n<header class=\"intro-header\" style=\"background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')\">\n\t<div class=\"container\">\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n\t\t\t\t<div class=\"site-heading\" id=\"tag-heading\">\n\t\t\t\t\t<h1>Offline</h1>\n\t\t\t\t\t<span class=\"subheading\">{{ page.description }}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</header>\n\n<script>\n\tdocument.body.classList.add('page-fullscreen');\n</script>\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"学谦PowerBI\",\n    \"title\": \"学谦PowerBI\",\n    \"author\": \"学谦 <xueqian@powerbipro.cn>\",\n    \"version\": \"1.7.0\",\n    \"homepage\": \"http://xueqiandata.github.io\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/xueqiandata/xueqiandata.github.io\"\n    },\n    \"bugs\": \"https://github.com/xueqiandata/xueqiandata.github.io/issues\",\n    \"devDependencies\": {\n        \"grunt\": \">=1.3.0\"\n        \"grunt-contrib-less\": \"~0.11.4\",\n        \"grunt-contrib-watch\": \"~0.6.1\",\n        \"grunt-banner\": \"~0.2.3\",\n        \"grunt-contrib-uglify\": \"~0.5.1\"\n    },\n    \"scripts\": {\n        \"preview\": \"cd _site; python -m SimpleHTTPServer 8020\",\n        \"py3view\": \"cd _site; python3 -m http.server 8020\",\n        \"watch\"  : \"grunt watch & npm run preview & jekyll serve -w\",\n        \"py3wa\"  : \"grunt watch & npm run py3view & jekyll serve -w\",\n        \"boil\"   : \"git push boilerplate boilerplate:master\",\n        \"push\"   : \"git push origin master --tag\",\n        \"cafe\"   : \"git co gitcafe-pages; git merge master; git push gitcafe gitcafe-pages:gitcafe-pages --tag; git co master;\"\n    }\n}\n"
  },
  {
    "path": "pwa/manifest.json",
    "content": "{\n  \"name\": \"BY Blog\",\n  \"short_name\": \"BY Blog\",\n  \"description\": \"A personal blog by engineer\",\n  \"icons\": [{\n    \"src\": \"icons/pwa_icon_128.png\",\n    \"sizes\": \"128x128\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"icons/pwa_icon_512.png\",\n    \"sizes\": \"512x512\",\n    \"type\": \"image/png\"\n  }],\n  \"background_color\": \"#fff\",\n  \"theme_color\": \"#000\",\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"orientation\": \"portrait\"\n}\n"
  },
  {
    "path": "sw.js",
    "content": "/* ===========================================================\n * sw.js\n * ===========================================================\n * Copyright 2016 @huxpro\n * Licensed under Apache 2.0 \n * Register service worker.\n * ========================================================== */\n\nconst PRECACHE = 'precache-v1';\nconst RUNTIME = 'runtime';\nconst HOSTNAME_WHITELIST = [\n  self.location.hostname,\n  \"huangxuan.me\",\n  \"yanshuo.io\",\n  \"cdnjs.cloudflare.com\"\n]\n\n\n// The Util Function to hack URLs of intercepted requests\nconst getFixedUrl = (req) => {\n  var now = Date.now();\n  url = new URL(req.url)\n\n  // 1. fixed http URL\n  // Just keep syncing with location.protocol \n  // fetch(httpURL) belongs to active mixed content. \n  // And fetch(httpRequest) is not supported yet.\n  url.protocol = self.location.protocol\n\n  // 2. add query for caching-busting.\n  // Github Pages served with Cache-Control: max-age=600\n  // max-age on mutable content is error-prone, with SW life of bugs can even extend.\n  // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.\n  // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190\n  url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;\n  return url.href\n}\n\n// The Util Function to detect and polyfill req.mode=\"navigate\"\n// request.mode of 'navigate' is unfortunately not supported in Chrome\n// versions older than 49, so we need to include a less precise fallback,\n// which checks for a GET request with an Accept: text/html header.\nconst isNavigationReq = (req) => (req.mode === 'navigate' || (req.method === 'GET' && req.headers.get('accept').includes('text/html')))\n\n// The Util Function to detect if a req is end with extension\n// Accordin to Fetch API spec <https://fetch.spec.whatwg.org/#concept-request-destination>\n// Any HTML's navigation has consistently mode=\"navigate\" type=\"\" and destination=\"document\" \n// including requesting an img (or any static resources) from URL Bar directly.\n// So It ends up with that regExp is still the king of URL routing ;)\n// P.S. An url.pathname has no '.' can not indicate it ends with extension (e.g. /api/version/1.2/)\nconst endWithExtension = (req) => Boolean(new URL(req.url).pathname.match(/\\.\\w+$/))\n\n// Redirect in SW manually fixed github pages arbitray 404s on things?blah \n// what we want:\n//    repo?blah -> !(gh 404) -> sw 302 -> repo/?blah \n//    .ext?blah -> !(sw 302 -> .ext/?blah -> gh 404) -> .ext?blah \n// If It's a navigation req and it's url.pathname isn't end with '/' or '.ext'\n// it should be a dir/repo request and need to be fixed (a.k.a be redirected)\n// Tracking https://twitter.com/Huxpro/status/798816417097224193\nconst shouldRedirect = (req) => (isNavigationReq(req) && new URL(req.url).pathname.substr(-1) !== \"/\" && !endWithExtension(req))\n\n// The Util Function to get redirect URL\n// `${url}/` would mis-add \"/\" in the end of query, so we use URL object.\n// P.P.S. Always trust url.pathname instead of the whole url string.\nconst getRedirectUrl = (req) => {\n  url = new URL(req.url)\n  url.pathname += \"/\"\n  return url.href\n}\n\n/**\n *  @Lifecycle Install\n *  Precache anything static to this version of your app.\n *  e.g. App Shell, 404, JS/CSS dependencies...\n *\n *  waitUntil() : installing ====> installed\n *  skipWaiting() : waiting(installed) ====> activating\n */\nself.addEventListener('install', e => {\n  e.waitUntil(\n    caches.open(PRECACHE).then(cache => {\n      return cache.add('offline.html')\n      .then(self.skipWaiting())\n      .catch(err => console.log(err))\n    })\n  )\n});\n\n\n/**\n *  @Lifecycle Activate\n *  New one activated when old isnt being used.\n *\n *  waitUntil(): activating ====> activated\n */\nself.addEventListener('activate',  event => {\n  console.log('service worker activated.')\n  event.waitUntil(self.clients.claim());\n});\n\n\n/**\n *  @Functional Fetch\n *  All network requests are being intercepted here.\n * \n *  void respondWith(Promise<Response> r);\n */\nself.addEventListener('fetch', event => {\n  // logs for debugging\n  console.log(`fetch ${event.request.url}`)\n  //console.log(` - type: ${event.request.type}; destination: ${event.request.destination}`)\n  //console.log(` - mode: ${event.request.mode}, accept: ${event.request.headers.get('accept')}`)\n\n  // Skip some of cross-origin requests, like those for Google Analytics.\n  if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {\n    \n    // Redirect in SW manually fixed github pages 404s on repo?blah \n    if(shouldRedirect(event.request)){\n      event.respondWith(Response.redirect(getRedirectUrl(event.request)))\n      return;\n    }\n\n    // Stale-while-revalidate \n    // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale\n    // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1\n    const cached = caches.match(event.request);\n    const fixedUrl = getFixedUrl(event.request);\n    const fetched = fetch(fixedUrl, {cache: \"no-store\"});\n    const fetchedCopy = fetched.then(resp => resp.clone());\n\n    // Call respondWith() with whatever we get first.\n    // If the fetch fails (e.g disconnected), wait for the cache.\n    // If there’s nothing in cache, wait for the fetch. \n    // If neither yields a response, return offline pages.\n    event.respondWith(\n      Promise.race([fetched.catch(_ => cached), cached])\n        .then(resp => resp || fetched)\n        .catch(_ => caches.match('offline.html'))\n    );\n\n    // Update the cache with the version we fetched (only for ok status)\n    event.waitUntil(\n      Promise.all([fetchedCopy, caches.open(RUNTIME)])\n        .then(([response, cache]) => response.ok && cache.put(event.request, response))\n        .catch(_ => {/* eat any errors */})\n    );\n  }\n});\n"
  },
  {
    "path": "tags.html",
    "content": "---\ntitle: Tags\nlayout: default\ndescription: keep hungry keep foolish\nheader-img: \"img/tag-bg.jpg\"\n---\n\n<!-- Page Header -->\n<header class=\"intro-header\" style=\"background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')\">\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                <div class=\"site-heading\" id=\"tag-heading\">\n                    <h1>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</h1>\n                    <span class=\"subheading\">{{ page.description }}</span>\n                </div>\n            </div>\n        </div>\n    </div>\n</header>\n\n<!-- Main Content -->\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n            <!-- 标签云 -->\n\t\t\t<div id='tag_cloud' class=\"tags\">\n\t\t\t\t{% for tag in site.tags %}\n\t\t\t\t<a href=\"#{{ tag[0] }}\" title=\"{{ tag[0] }}\" rel=\"{{ tag[1].size }}\">{{ tag[0] }}</a>\n\t\t\t\t{% endfor %}\n\t\t\t</div>\n\n            <!--<blockquote class=\"tag-comments\">\n                标签命名规范：\n                    <li>行业观察、职位、公司优先使用中文</li>\n                    <li>外国产品、术语优先使用英文</li>\n            </blockquote>-->\n\n            <!-- 标签列表 -->\n\t\t\t{% for tag in site.tags %}\n\t\t\t<div class=\"one-tag-list\">\n\t\t\t  \t<span class=\"fa fa-tag listing-seperator\" id=\"{{ tag[0] }}\">\n                    <span class=\"tag-text\">{{ tag[0] }}</span>\n                </span>\n\t\t\t\t{% for post in tag[1] %}\n\t\t\t\t  <!-- <li class=\"listing-item\">\n\t\t\t\t  <time datetime=\"{{ post.date | date:\"%Y-%m-%d\" }}\">{{ post.date | date:\"%Y-%m-%d\" }}</time>\n\t\t\t\t  <a href=\"{{ post.url }}\" title=\"{{ post.title }}\">{{ post.title }}</a>\n\t\t\t\t  </li> -->\n\t\t\t\t <div class=\"post-preview\">\n\t\t\t\t    <a href=\"{{ post.url | prepend: site.baseurl }}\">\n\t\t\t\t        <h2 class=\"post-title\">\n                            {{ post.title }}\n\t\t\t\t        </h2>\n\t\t\t\t        {% if post.subtitle %}\n\t\t\t\t        <h3 class=\"post-subtitle\">\n\t\t\t\t            {{ post.subtitle }}\n\t\t\t\t        </h3>\n\t\t\t\t        {% endif %}\n\t\t\t\t    </a>\n\t\t\t\t    <!-- <p class=\"post-meta\">{{ post.date | date:\"%Y-%m-%d\" }}</p> -->\n\t\t\t\t</div>\n\t\t\t\t<hr>\n\t\t\t\t{% endfor %}\n\t\t\t</div>\n\t\t\t{% endfor %}\n\n\t\t</div>\n\t</div>\n</div>\n"
  }
]