├── .gitignore ├── dist ├── .gitignore └── ng-staticize.js ├── webpack.config.js ├── HOW.MD ├── scripts └── pack-demo.js ├── .eslintrc ├── package.json ├── LICENSE ├── src ├── index.js ├── parser.js └── builder.js ├── README.MD └── examples └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !ng-staticize.js -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var plugins = []; 2 | 3 | if (process.env.COMPRESS) { 4 | plugins.push( 5 | new webpack.optimize.UglifyJsPlugin({}) 6 | ); 7 | } 8 | 9 | module.exports = { 10 | entry: './src/index.js', 11 | output: { 12 | path: './dist', 13 | filename: 'ng-staticize.js' 14 | }, 15 | module: { 16 | preLoaders: [ 17 | { test: /\.js$/, exclude: /node_modules|bower_components/, loader: 'eslint-loader' } 18 | ] 19 | }, 20 | plugins: plugins 21 | }; -------------------------------------------------------------------------------- /HOW.MD: -------------------------------------------------------------------------------- 1 | ## 起因 2 | Angular的模板在数据量较大(比如渲染一个2000条数据表格)可能会遇到渲染速度比较慢的问题,[bindonce](https://github.com/Pasvaz/bindonce)提供了一个解决方案,用来解决页面中因为watcher数量较多造成的性能问题。 3 | 4 | 在IE8下,DOM操作是一个非常昂贵的操作,你可以阅读这个stack overflow上这个[问答](http://stackoverflow.com/questions/9639703/ie8-javascript-very-slow-to-load-large-list-of-options-in-select-element)来了解其中的细节。 5 | 6 | 简单来讲,在IE8下创建6000个option元素,需要花费16s。这个问题的解决办法就是使用innerHTML来创建option,不使用createElement来创建option。 7 | 8 | ## 解决办法 9 | 10 | ng-staticize做的事情非常简单,自己解析了Angular的模板,并通过解析后的模板构建出要输出的HTML。 -------------------------------------------------------------------------------- /scripts/pack-demo.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var useMin = require('usemin-lib'); 4 | const EXAMPLES_PATH = './examples'; 5 | const DIST_PATH = './dist/'; 6 | 7 | var entries = ['index.html']; 8 | 9 | entries.map(function(fileName) { 10 | var entry = path.join(EXAMPLES_PATH, fileName); 11 | var content = fs.readFileSync(entry).toString(); 12 | var blocks = useMin.getBlocks(entry, content, true); 13 | useMin.processBlocks(blocks, DIST_PATH); 14 | fs.writeFile(DIST_PATH + fileName, useMin.getHTML(content, blocks, false)); 15 | }); 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 2, 6 | { 7 | "SwitchCase": 1 8 | } 9 | ], 10 | "quotes": [ 11 | 2, 12 | "single" 13 | ], 14 | "linebreak-style": [ 15 | 2, 16 | "unix" 17 | ], 18 | "semi": [ 19 | 2, 20 | "always" 21 | ] 22 | }, 23 | "env": { 24 | "es6": true, 25 | "browser": true, 26 | "commonjs": true 27 | }, 28 | "extends": "eslint:recommended", 29 | "ecmaFeatures": { 30 | "jsx": true 31 | }, 32 | "globals": { 33 | "angular": true, 34 | "process": true, 35 | "webpack": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-staticize", 3 | "version": "0.1.0", 4 | "description": "Staticize your angular template. Zero watcher and fast as template engine.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "node_modules/.bin/webpack --watch --devtool inline-source-map", 8 | "dist": "COMPRESS=1 node_modules/.bin/webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/ElemeFE/ng-staticize.git" 13 | }, 14 | "dependencies": { 15 | "domify": "^1.4.0" 16 | }, 17 | "devDependencies": { 18 | "angular": "1.2.28", 19 | "bootstrap": "^3.3.5", 20 | "eslint": "^1.5.1", 21 | "eslint-loader": "^1.0.0", 22 | "jquery": "1.11.3", 23 | "mocha": "~2.2.5", 24 | "should": "~7.0.2", 25 | "sinon": "~1.10.2", 26 | "usemin-lib": "0.0.5", 27 | "webpack": "^1.9.10" 28 | }, 29 | "author": "long.zhang", 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 饿了么前端 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var parser = require('./parser'); 2 | var builder = require('./builder'); 3 | 4 | angular.module('ngStaticize', []) 5 | .factory('templateParser', parser) 6 | .factory('templateBuilder', builder) 7 | .directive('ngStaticize', ['templateParser', 'templateBuilder', '$parse', function(templateParser, templateBuilder) { 8 | return { 9 | restrict: 'EA', 10 | replace: true, 11 | terminal: true, 12 | priority: 1001, 13 | compile: function(element) { 14 | var html = element[0].innerHTML; 15 | var template = templateParser.parse(html); 16 | return { 17 | post: function(scope, element, attrs) { 18 | var reRender = function () { 19 | var result = templateBuilder.build(template, scope); 20 | element[0].innerHTML = result; 21 | }; 22 | 23 | reRender(); 24 | 25 | var watchExpr = attrs.ngStaticize; 26 | 27 | if (watchExpr) { 28 | scope.$watch(watchExpr, function() { 29 | reRender(); 30 | }, true); 31 | } 32 | } 33 | }; 34 | } 35 | }; 36 | }]); 37 | 38 | module.exports = {}; -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Introduce 2 | 3 | ng-staticize 是一个把 Angular 模板静态化的 directive,使用之后 watcher 数量会显著降低,渲染速度(在数据量较大的情况下)在 IE8 上会有10-20x的性能提升。 4 | 5 | 你可以花几分钟查看 [这个例子](http://elemefe.github.io/ng-staticize/) 来体验一下 ng-staticize 带来的性能提升,在 IE8 或者 Firefox 下会看到更明显的性能提升。 6 | 7 | 从降低 Angular watcher 的角度来看,ng-staticize 是一个与 [bindonce](https://github.com/Pasvaz/bindonce) 类似的项目,区别在于 ng-staticize 无需改变模板中 directive 的定义。 8 | 9 | 从设计之初,ng-staticize 就存在以下缺陷,请知悉: 10 | 11 | 1. 应用 ng-staticize 的区域中不能使用 directive 兼容列表以外的 directive。 12 | 2. 渲染出的 DOM 不再动态,即在 Controller 中的数据变更后,应用了 ng-staticize 的区域不会重新渲染。不过你可以为 ng-staticize 指定一个表达式,在表达式变更后这个区域会重新渲染。 13 | 14 | 以下是 ng-staticize 适用场景: 15 | 16 | 1. 页面动态渲染比较少,类似于静态页面,只是使用了 Angular 的模板来描述数据绑定。 17 | 2. 页面中需要渲染的数据较多,需要做性能优化。 18 | 19 | 如果你对 ng-staticize 的实现细节感兴趣,你可以阅读 [这篇文档](HOW.MD)。 20 | 21 | # Usage 22 | 23 | ## Install 24 | 25 | 如果你使用 Browerify 或者 Webpack,可以使用 npm 来安装 ng-staticize: 26 | 27 | ```Bash 28 | npm install ng-staticize —save 29 | ``` 30 | 31 | 如果没有使用 npm,则可以下载项目后,在项目中引用 dist 下的 ng-staticize.js。 32 | 33 | ## Include ngStaticize 34 | 35 | ```JavaScript 36 | angular.module('demo', [ 'ngStaticize' ]); 37 | ``` 38 | 39 | ## Use 40 | 41 | 如果只是想把页面中的某一个页面中的某个区域进行静态化操作,只需要为这个区域的元素添加一个属性:ng-staticize。 42 | 43 | ```HTML 44 |
...
45 | ``` 46 | 47 | 如果需要在某些数据变更之后重新渲染这块区域,为 ng-staticize 属性设置一个表达式,在该表达式变更之后这个区域会重新渲染。比如在 scope 中的 todos 属性发生了变化之后重新渲染,则这么定义: 48 | 49 | ```HTML 50 |
...
51 | ``` 52 | 53 | > 如果需要兼容低版本浏 IE 览器(IE8、IE9),请不要在 table、tbody、tfoot、thead、title、tr 等元素上使用 ng-staticize,原因见 [此文章](http://w3help.org/zh-cn/causes/BX9046)。 54 | 55 | # Compatible Directive 56 | ng-staticize 只兼容了一些常见的 directive,列表如下: 57 | 58 | - ng-if 59 | - ng-repeat 60 | - ng-style 61 | - ng-class 62 | - ng-show 63 | - ng-hide 64 | - ng-html 65 | - ng-bind 66 | - ng-text 67 | - ng-src 68 | - ng-href 69 | - ng-alt 70 | - ng-title 71 | - ng-id 72 | - ng-disabled 73 | - ng-value 74 | 75 | # Fork 76 | 如果你想修改 ng-staticize 的代码,在项目文件夹下执行这两个命令: 77 | 78 | ```Bash 79 | npm install 80 | npm run dev 81 | ``` 82 | 83 | # License 84 | MIT 85 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | var templateParser = ['$parse', '$interpolate', function($parse, $interpolate) { 2 | var needInterpolate = { 'ng-src': true, 'ng-href': true }; 3 | var TEXT_NODE = 3; 4 | var ELEMENT_NODE = 1; 5 | 6 | function walk(el) { 7 | if (el.nodeType !== ELEMENT_NODE) return; 8 | 9 | var children = []; 10 | var node = { tagName: el.tagName.toLowerCase() }; 11 | var attributes = el.attributes; 12 | var attrs, dirs, i, j; 13 | 14 | for (i = 0, j = attributes.length; i < j; i++) { 15 | var attribute = attributes[i]; 16 | var name = attribute.name; 17 | var value = attribute.nodeValue; 18 | if (name && name.substr(0, 3) === 'ng-') { 19 | if (!dirs) { 20 | dirs = {}; 21 | } 22 | if (name.length > 8 && name.substr(0, 8) === 'ng-attr-') { 23 | if (!attrs) { 24 | attrs = {}; 25 | } 26 | attrs[name.substr(9)] = $parse(value); 27 | } else if (name === 'ng-repeat') { 28 | var matches = /(\w+)\s+in\s+(.*?)$/.exec(value); 29 | if (matches) { 30 | dirs[name] = { 31 | itemName: matches[1], 32 | getArray: $parse(matches[2]) 33 | }; 34 | } 35 | } else { 36 | dirs[name] = needInterpolate[name] ? $interpolate(value) : $parse(value); 37 | } 38 | } else { 39 | if (!attrs) { 40 | attrs = {}; 41 | } 42 | if (/\s*({{\s*(.+?)\s*}})\s*/gi.test(value)) { 43 | attrs[name] = $interpolate(value); 44 | } else { 45 | attrs[name] = value; 46 | } 47 | } 48 | } 49 | 50 | var childNodes = el.childNodes; 51 | for (i = 0, j = childNodes.length; i < j; i++) { 52 | var child = childNodes[i]; 53 | if (child.nodeType === TEXT_NODE) { 54 | var text = child.nodeValue; 55 | if (text) { 56 | if (/\s*({{\s*(.+?)\s*}})\s*/gi.test(text)) { 57 | children.push($interpolate(text)); 58 | } else { 59 | text = text.replace(/(\r\n|\n|\r|\s)/gm, ''); 60 | if (text.length) { 61 | children.push(text); 62 | } 63 | } 64 | } 65 | continue; 66 | } 67 | var parseResult = walk(child); 68 | if (parseResult) { 69 | children.push(parseResult); 70 | } 71 | } 72 | 73 | if (children.length > 0) { 74 | node.children = children; 75 | } 76 | 77 | if (attrs) { 78 | node.attrs = attrs; 79 | } 80 | 81 | if (dirs) { 82 | node.dirs = dirs; 83 | } 84 | 85 | return node; 86 | } 87 | 88 | return { 89 | parse: function(template) { 90 | return walk(require('domify')(template)); 91 | } 92 | }; 93 | }]; 94 | 95 | module.exports = templateParser; -------------------------------------------------------------------------------- /src/builder.js: -------------------------------------------------------------------------------- 1 | var trim = function (string) { 2 | return string.replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); 3 | }; 4 | 5 | var templateBuilder = [function() { 6 | function shallowClone(object) { 7 | var result = {}; 8 | for (var prop in object) { 9 | if (object.hasOwnProperty(prop)) result[prop] = object[prop]; 10 | } 11 | return result; 12 | } 13 | 14 | function newContext(context) { 15 | var empty = function() {}; 16 | empty.prototype = context; 17 | 18 | return new empty(); 19 | } 20 | 21 | function getStyle(object) { 22 | var cssText = ''; 23 | for (var prop in object) { 24 | if (object.hasOwnProperty(prop)) { 25 | cssText += prop + ':' + object[prop] + ';'; 26 | } 27 | } 28 | return cssText; 29 | } 30 | 31 | function getClasses(object) { 32 | var classes = []; 33 | for (var prop in object) { 34 | if (object.hasOwnProperty(prop) && !!object[prop]) { 35 | classes.push(prop); 36 | } 37 | } 38 | return classes.join(' '); 39 | } 40 | 41 | function cloneRepeatNode(node) { 42 | var cloneNode = shallowClone(node); 43 | cloneNode.dirs = shallowClone(node.dirs); 44 | cloneNode.dirs['ng-repeat'] = null; 45 | delete cloneNode.dirs['ng-repeat']; 46 | 47 | return cloneNode; 48 | } 49 | 50 | var ATTR_DIR_MAP = { 51 | 'ng-src': true, 52 | 'ng-href': true, 53 | 'ng-alt': true, 54 | 'ng-title': true, 55 | 'ng-id': true, 56 | 'ng-disabled': true, 57 | 'ng-value': true 58 | }; 59 | 60 | var HTML_DIR_MAP = { 61 | 'ng-html': true, 62 | 'ng-bind': true, 63 | 'ng-text': true 64 | }; 65 | 66 | var emptyArray = []; 67 | 68 | function toHTML(node, context) { 69 | var html, i, j; 70 | if (node instanceof Array) { 71 | html = ''; 72 | 73 | for (i = 0, j = node.length; i < j; i++) { 74 | html += toHTML(node[i], context); 75 | } 76 | 77 | return html; 78 | } 79 | 80 | if (typeof node === 'string') return node; 81 | if (typeof node === 'function') return node(context); 82 | 83 | var tag = node.tagName; 84 | var children = node.children; 85 | var attrs = node.attrs; 86 | var dirs = node.dirs; 87 | var content = node.textContent; 88 | var classes = ''; 89 | var cssText = ''; 90 | 91 | if (dirs && dirs['ng-repeat']) { 92 | var cloneNode = cloneRepeatNode(node); 93 | var repeatDir = dirs['ng-repeat']; 94 | var array = repeatDir.getArray(context) || emptyArray; 95 | var itemName = repeatDir.itemName; 96 | 97 | html = ''; 98 | 99 | for (i = 0, j = array.length; i < j; i++) { 100 | var subContext = newContext(context); 101 | subContext.$index = i; 102 | subContext.$first = i === 0; 103 | subContext.$last = i === j - 1; 104 | subContext.$middle = !(subContext.$first || subContext.$last); 105 | subContext[itemName] = array[i]; 106 | 107 | html += toHTML(cloneNode, subContext); 108 | } 109 | 110 | return html; 111 | } 112 | 113 | html = '<' + tag; 114 | 115 | for (var dir in dirs) { 116 | if (dirs.hasOwnProperty(dir)) { 117 | var fn = dirs[dir]; 118 | var value; 119 | if (typeof fn === 'function') { 120 | value = fn(context); 121 | } 122 | 123 | if (dir === 'ng-if') { 124 | if (!value) { 125 | return ''; 126 | } 127 | } else if (ATTR_DIR_MAP[dir]) { 128 | html += ' ' + dir.substr(3) + '="' + value + '"'; 129 | } else if (HTML_DIR_MAP[dir]) { 130 | content = value; 131 | } else if (dir === 'ng-style') { 132 | cssText = getStyle(value) + cssText; 133 | } else if (dir === 'ng-class') { 134 | classes += getClasses(value); 135 | } else if (dir === 'ng-show' || dir === 'ng-hide') { 136 | if (value !== null && value !== undefined) { 137 | classes += ' ' + dir; 138 | } 139 | } 140 | } 141 | } 142 | 143 | for (var attr in attrs) { 144 | if (attrs.hasOwnProperty(attr)) { 145 | if (attr === 'style') continue; 146 | 147 | var attrValue = attrs[attr]; 148 | if (typeof attrValue === 'function') { 149 | attrValue = attrValue(context); 150 | } 151 | if (attr === 'class') { 152 | classes += ' ' + attrValue; 153 | } else { 154 | html += ' ' + attr + '="' + attrValue + '"'; 155 | } 156 | } 157 | } 158 | 159 | if (classes) { 160 | html += ' class="' + trim(classes) + '"'; 161 | } 162 | 163 | if (cssText) { 164 | html += ' style="' + cssText + '"'; 165 | } 166 | 167 | html += '>'; 168 | 169 | if (content) { 170 | html += content; 171 | } 172 | 173 | if (children) { 174 | for (i = 0, j = children.length; i < j; i++) { 175 | var child = children[i]; 176 | html += toHTML(child, context); 177 | } 178 | } 179 | 180 | html += ''; 181 | 182 | return html; 183 | } 184 | 185 | return { 186 | build: toHTML 187 | }; 188 | }]; 189 | 190 | module.exports = templateBuilder; -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ng Staticize Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 72 | 73 | 74 | 75 | 76 | 77 | 172 | 173 |
174 | 175 |

ng-staticize demo

176 |
177 |

This demo demonstrates the render speed of ng-staticize. Switch tabs to view the render speed of using ng-staticize or not.

178 |

If you are using Chrome, you can use Angular watchers to view the watcher's change.

179 |
180 | 181 | 185 | 186 | 187 |
188 |
189 | 190 |
191 |
192 | 193 | 194 | 195 | 196 | 197 |
198 | 199 | 203 |
204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 225 | 226 | 227 |
#First NameLast NameAgeGenderProfile
221 | 222 | 223 | 224 |
228 |
229 |
230 |
231 |
232 |
233 |
234 | 235 | 236 | 237 | 238 | 239 |
240 | 241 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 267 | 268 | 269 |
#First NameLast NameAgeGenderProfile
263 | 264 | 265 | 266 |
270 |
271 |
272 |
273 |
274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /dist/ng-staticize.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | /******/ 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ function(module, exports, __webpack_require__) { 46 | 47 | var parser = __webpack_require__(1); 48 | var builder = __webpack_require__(3); 49 | 50 | angular.module('ngStaticize', []) 51 | .factory('templateParser', parser) 52 | .factory('templateBuilder', builder) 53 | .directive('ngStaticize', ['templateParser', 'templateBuilder', '$parse', function(templateParser, templateBuilder) { 54 | return { 55 | restrict: 'EA', 56 | replace: true, 57 | terminal: true, 58 | priority: 1001, 59 | compile: function(element) { 60 | var html = element[0].innerHTML; 61 | var template = templateParser.parse(html); 62 | return { 63 | post: function(scope, element, attrs) { 64 | var reRender = function () { 65 | var result = templateBuilder.build(template, scope); 66 | element[0].innerHTML = result; 67 | }; 68 | 69 | reRender(); 70 | 71 | var watchExpr = attrs.ngStaticize; 72 | 73 | if (watchExpr) { 74 | scope.$watch(watchExpr, function() { 75 | reRender(); 76 | }, true); 77 | } 78 | } 79 | }; 80 | } 81 | }; 82 | }]); 83 | 84 | module.exports = {}; 85 | 86 | /***/ }, 87 | /* 1 */ 88 | /***/ function(module, exports, __webpack_require__) { 89 | 90 | var templateParser = ['$parse', '$interpolate', function($parse, $interpolate) { 91 | var needInterpolate = { 'ng-src': true, 'ng-href': true }; 92 | var TEXT_NODE = 3; 93 | var ELEMENT_NODE = 1; 94 | 95 | function walk(el) { 96 | if (el.nodeType !== ELEMENT_NODE) return; 97 | 98 | var children = []; 99 | var node = { tagName: el.tagName.toLowerCase() }; 100 | var attributes = el.attributes; 101 | var attrs, dirs, i, j; 102 | 103 | for (i = 0, j = attributes.length; i < j; i++) { 104 | var attribute = attributes[i]; 105 | var name = attribute.name; 106 | var value = attribute.nodeValue; 107 | if (name && name.substr(0, 3) === 'ng-') { 108 | if (!dirs) { 109 | dirs = {}; 110 | } 111 | if (name.length > 8 && name.substr(0, 8) === 'ng-attr-') { 112 | if (!attrs) { 113 | attrs = {}; 114 | } 115 | attrs[name.substr(9)] = $parse(value); 116 | } else if (name === 'ng-repeat') { 117 | var matches = /(\w+)\s+in\s+(.*?)$/.exec(value); 118 | if (matches) { 119 | dirs[name] = { 120 | itemName: matches[1], 121 | getArray: $parse(matches[2]) 122 | }; 123 | } 124 | } else { 125 | dirs[name] = needInterpolate[name] ? $interpolate(value) : $parse(value); 126 | } 127 | } else { 128 | if (!attrs) { 129 | attrs = {}; 130 | } 131 | if (/\s*({{\s*(.+?)\s*}})\s*/gi.test(value)) { 132 | attrs[name] = $interpolate(value); 133 | } else { 134 | attrs[name] = value; 135 | } 136 | } 137 | } 138 | 139 | var childNodes = el.childNodes; 140 | for (i = 0, j = childNodes.length; i < j; i++) { 141 | var child = childNodes[i]; 142 | if (child.nodeType === TEXT_NODE) { 143 | var text = child.nodeValue; 144 | if (text) { 145 | if (/\s*({{\s*(.+?)\s*}})\s*/gi.test(text)) { 146 | children.push($interpolate(text)); 147 | } else { 148 | text = text.replace(/(\r\n|\n|\r|\s)/gm, ''); 149 | if (text.length) { 150 | children.push(text); 151 | } 152 | } 153 | } 154 | continue; 155 | } 156 | var parseResult = walk(child); 157 | if (parseResult) { 158 | children.push(parseResult); 159 | } 160 | } 161 | 162 | if (children.length > 0) { 163 | node.children = children; 164 | } 165 | 166 | if (attrs) { 167 | node.attrs = attrs; 168 | } 169 | 170 | if (dirs) { 171 | node.dirs = dirs; 172 | } 173 | 174 | return node; 175 | } 176 | 177 | return { 178 | parse: function(template) { 179 | return walk(__webpack_require__(2)(template)); 180 | } 181 | }; 182 | }]; 183 | 184 | module.exports = templateParser; 185 | 186 | /***/ }, 187 | /* 2 */ 188 | /***/ function(module, exports) { 189 | 190 | 191 | /** 192 | * Expose `parse`. 193 | */ 194 | 195 | module.exports = parse; 196 | 197 | /** 198 | * Tests for browser support. 199 | */ 200 | 201 | var innerHTMLBug = false; 202 | var bugTestDiv; 203 | if (typeof document !== 'undefined') { 204 | bugTestDiv = document.createElement('div'); 205 | // Setup 206 | bugTestDiv.innerHTML = '
a'; 207 | // Make sure that link elements get serialized correctly by innerHTML 208 | // This requires a wrapper element in IE 209 | innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length; 210 | bugTestDiv = undefined; 211 | } 212 | 213 | /** 214 | * Wrap map from jquery. 215 | */ 216 | 217 | var map = { 218 | legend: [1, '
', '
'], 219 | tr: [2, '', '
'], 220 | col: [2, '', '
'], 221 | // for script/link/style tags to work in IE6-8, you have to wrap 222 | // in a div with a non-whitespace character in front, ha! 223 | _default: innerHTMLBug ? [1, 'X
', '
'] : [0, '', ''] 224 | }; 225 | 226 | map.td = 227 | map.th = [3, '', '
']; 228 | 229 | map.option = 230 | map.optgroup = [1, '']; 231 | 232 | map.thead = 233 | map.tbody = 234 | map.colgroup = 235 | map.caption = 236 | map.tfoot = [1, '', '
']; 237 | 238 | map.polyline = 239 | map.ellipse = 240 | map.polygon = 241 | map.circle = 242 | map.text = 243 | map.line = 244 | map.path = 245 | map.rect = 246 | map.g = [1, '','']; 247 | 248 | /** 249 | * Parse `html` and return a DOM Node instance, which could be a TextNode, 250 | * HTML DOM Node of some kind (
for example), or a DocumentFragment 251 | * instance, depending on the contents of the `html` string. 252 | * 253 | * @param {String} html - HTML string to "domify" 254 | * @param {Document} doc - The `document` instance to create the Node for 255 | * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance 256 | * @api private 257 | */ 258 | 259 | function parse(html, doc) { 260 | if ('string' != typeof html) throw new TypeError('String expected'); 261 | 262 | // default to the global `document` object 263 | if (!doc) doc = document; 264 | 265 | // tag name 266 | var m = /<([\w:]+)/.exec(html); 267 | if (!m) return doc.createTextNode(html); 268 | 269 | html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace 270 | 271 | var tag = m[1]; 272 | 273 | // body support 274 | if (tag == 'body') { 275 | var el = doc.createElement('html'); 276 | el.innerHTML = html; 277 | return el.removeChild(el.lastChild); 278 | } 279 | 280 | // wrap map 281 | var wrap = map[tag] || map._default; 282 | var depth = wrap[0]; 283 | var prefix = wrap[1]; 284 | var suffix = wrap[2]; 285 | var el = doc.createElement('div'); 286 | el.innerHTML = prefix + html + suffix; 287 | while (depth--) el = el.lastChild; 288 | 289 | // one element 290 | if (el.firstChild == el.lastChild) { 291 | return el.removeChild(el.firstChild); 292 | } 293 | 294 | // several elements 295 | var fragment = doc.createDocumentFragment(); 296 | while (el.firstChild) { 297 | fragment.appendChild(el.removeChild(el.firstChild)); 298 | } 299 | 300 | return fragment; 301 | } 302 | 303 | 304 | /***/ }, 305 | /* 3 */ 306 | /***/ function(module, exports) { 307 | 308 | var trim = function (string) { 309 | return string.replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); 310 | }; 311 | 312 | var templateBuilder = [function() { 313 | function shallowClone(object) { 314 | var result = {}; 315 | for (var prop in object) { 316 | if (object.hasOwnProperty(prop)) result[prop] = object[prop]; 317 | } 318 | return result; 319 | } 320 | 321 | function newContext(context) { 322 | var empty = function() {}; 323 | empty.prototype = context; 324 | 325 | return new empty(); 326 | } 327 | 328 | function getStyle(object) { 329 | var cssText = ''; 330 | for (var prop in object) { 331 | if (object.hasOwnProperty(prop)) { 332 | cssText += prop + ':' + object[prop] + ';'; 333 | } 334 | } 335 | return cssText; 336 | } 337 | 338 | function getClasses(object) { 339 | var classes = []; 340 | for (var prop in object) { 341 | if (object.hasOwnProperty(prop) && !!object[prop]) { 342 | classes.push(prop); 343 | } 344 | } 345 | return classes.join(' '); 346 | } 347 | 348 | function cloneRepeatNode(node) { 349 | var cloneNode = shallowClone(node); 350 | cloneNode.dirs = shallowClone(node.dirs); 351 | cloneNode.dirs['ng-repeat'] = null; 352 | delete cloneNode.dirs['ng-repeat']; 353 | 354 | return cloneNode; 355 | } 356 | 357 | var ATTR_DIR_MAP = { 358 | 'ng-src': true, 359 | 'ng-href': true, 360 | 'ng-alt': true, 361 | 'ng-title': true, 362 | 'ng-id': true, 363 | 'ng-disabled': true, 364 | 'ng-value': true 365 | }; 366 | 367 | var HTML_DIR_MAP = { 368 | 'ng-html': true, 369 | 'ng-bind': true, 370 | 'ng-text': true 371 | }; 372 | 373 | var emptyArray = []; 374 | 375 | function toHTML(node, context) { 376 | var html, i, j; 377 | if (node instanceof Array) { 378 | html = ''; 379 | 380 | for (i = 0, j = node.length; i < j; i++) { 381 | html += toHTML(node[i], context); 382 | } 383 | 384 | return html; 385 | } 386 | 387 | if (typeof node === 'string') return node; 388 | if (typeof node === 'function') return node(context); 389 | 390 | var tag = node.tagName; 391 | var children = node.children; 392 | var attrs = node.attrs; 393 | var dirs = node.dirs; 394 | var content = node.textContent; 395 | var classes = ''; 396 | var cssText = ''; 397 | 398 | if (dirs && dirs['ng-repeat']) { 399 | var cloneNode = cloneRepeatNode(node); 400 | var repeatDir = dirs['ng-repeat']; 401 | var array = repeatDir.getArray(context) || emptyArray; 402 | var itemName = repeatDir.itemName; 403 | 404 | html = ''; 405 | 406 | for (i = 0, j = array.length; i < j; i++) { 407 | var subContext = newContext(context); 408 | subContext.$index = i; 409 | subContext.$first = i === 0; 410 | subContext.$last = i === j - 1; 411 | subContext.$middle = !(subContext.$first || subContext.$last); 412 | subContext[itemName] = array[i]; 413 | 414 | html += toHTML(cloneNode, subContext); 415 | } 416 | 417 | return html; 418 | } 419 | 420 | html = '<' + tag; 421 | 422 | for (var dir in dirs) { 423 | if (dirs.hasOwnProperty(dir)) { 424 | var fn = dirs[dir]; 425 | var value; 426 | if (typeof fn === 'function') { 427 | value = fn(context); 428 | } 429 | 430 | if (dir === 'ng-if') { 431 | if (!value) { 432 | return ''; 433 | } 434 | } else if (ATTR_DIR_MAP[dir]) { 435 | html += ' ' + dir.substr(3) + '="' + value + '"'; 436 | } else if (HTML_DIR_MAP[dir]) { 437 | content = value; 438 | } else if (dir === 'ng-style') { 439 | cssText = getStyle(value) + cssText; 440 | } else if (dir === 'ng-class') { 441 | classes += getClasses(value); 442 | } else if (dir === 'ng-show' || dir === 'ng-hide') { 443 | if (value !== null && value !== undefined) { 444 | classes += ' ' + dir; 445 | } 446 | } 447 | } 448 | } 449 | 450 | for (var attr in attrs) { 451 | if (attrs.hasOwnProperty(attr)) { 452 | if (attr === 'style') continue; 453 | 454 | var attrValue = attrs[attr]; 455 | if (typeof attrValue === 'function') { 456 | attrValue = attrValue(context); 457 | } 458 | if (attr === 'class') { 459 | classes += ' ' + attrValue; 460 | } else { 461 | html += ' ' + attr + '="' + attrValue + '"'; 462 | } 463 | } 464 | } 465 | 466 | if (classes) { 467 | html += ' class="' + trim(classes) + '"'; 468 | } 469 | 470 | if (cssText) { 471 | html += ' style="' + cssText + '"'; 472 | } 473 | 474 | html += '>'; 475 | 476 | if (content) { 477 | html += content; 478 | } 479 | 480 | if (children) { 481 | for (i = 0, j = children.length; i < j; i++) { 482 | var child = children[i]; 483 | html += toHTML(child, context); 484 | } 485 | } 486 | 487 | html += ''; 488 | 489 | return html; 490 | } 491 | 492 | return { 493 | build: toHTML 494 | }; 495 | }]; 496 | 497 | module.exports = templateBuilder; 498 | 499 | /***/ } 500 | /******/ ]); 501 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgZjdkNTNjOTk2N2FhMTA3MWQ2NzAiLCJ3ZWJwYWNrOi8vLy4vc3JjL2luZGV4LmpzIiwid2VicGFjazovLy8uL3NyYy9wYXJzZXIuanMiLCJ3ZWJwYWNrOi8vLy4vfi9kb21pZnkvaW5kZXguanMiLCJ3ZWJwYWNrOi8vLy4vc3JjL2J1aWxkZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLHVCQUFlO0FBQ2Y7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN0Q0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsZ0JBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSCxxQjs7Ozs7O0FDckNBO0FBQ0EsMEJBQXlCO0FBQ3pCO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBLGlCQUFnQjtBQUNoQjtBQUNBOztBQUVBLHVDQUFzQyxPQUFPO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQixhQUFhO0FBQ2hDO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsdUNBQXNDLE9BQU87QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsYUFBYTtBQUNsQztBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxFQUFDOztBQUVELGlDOzs7Ozs7O0FDN0ZBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVcsT0FBTztBQUNsQixZQUFXLFNBQVM7QUFDcEIsYUFBWSxRQUFRO0FBQ3BCO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQSx5Q0FBd0M7O0FBRXhDOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQy9HQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtEQUFpRDtBQUNqRDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLG1DQUFrQyxPQUFPO0FBQ3pDO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUEsb0NBQW1DLE9BQU87QUFDMUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBLFVBQVM7QUFDVDtBQUNBLFVBQVM7QUFDVDtBQUNBLFVBQVM7QUFDVDtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLHVDQUFzQyxPQUFPO0FBQzdDO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsRUFBQzs7QUFFRCxrQyIsImZpbGUiOiJuZy1zdGF0aWNpemUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSlcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcblxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0ZXhwb3J0czoge30sXG4gXHRcdFx0aWQ6IG1vZHVsZUlkLFxuIFx0XHRcdGxvYWRlZDogZmFsc2VcbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubG9hZGVkID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXygwKTtcblxuXG5cbi8qKiBXRUJQQUNLIEZPT1RFUiAqKlxuICoqIHdlYnBhY2svYm9vdHN0cmFwIGY3ZDUzYzk5NjdhYTEwNzFkNjcwXG4gKiovIiwidmFyIHBhcnNlciA9IHJlcXVpcmUoJy4vcGFyc2VyJyk7XG52YXIgYnVpbGRlciA9IHJlcXVpcmUoJy4vYnVpbGRlcicpO1xuXG5hbmd1bGFyLm1vZHVsZSgnbmdTdGF0aWNpemUnLCBbXSlcbiAgLmZhY3RvcnkoJ3RlbXBsYXRlUGFyc2VyJywgcGFyc2VyKVxuICAuZmFjdG9yeSgndGVtcGxhdGVCdWlsZGVyJywgYnVpbGRlcilcbiAgLmRpcmVjdGl2ZSgnbmdTdGF0aWNpemUnLCBbJ3RlbXBsYXRlUGFyc2VyJywgJ3RlbXBsYXRlQnVpbGRlcicsICckcGFyc2UnLCBmdW5jdGlvbih0ZW1wbGF0ZVBhcnNlciwgdGVtcGxhdGVCdWlsZGVyKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHJlc3RyaWN0OiAnRUEnLFxuICAgICAgcmVwbGFjZTogdHJ1ZSxcbiAgICAgIHRlcm1pbmFsOiB0cnVlLFxuICAgICAgcHJpb3JpdHk6IDEwMDEsXG4gICAgICBjb21waWxlOiBmdW5jdGlvbihlbGVtZW50KSB7XG4gICAgICAgIHZhciBodG1sID0gZWxlbWVudFswXS5pbm5lckhUTUw7XG4gICAgICAgIHZhciB0ZW1wbGF0ZSA9IHRlbXBsYXRlUGFyc2VyLnBhcnNlKGh0bWwpO1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHBvc3Q6IGZ1bmN0aW9uKHNjb3BlLCBlbGVtZW50LCBhdHRycykge1xuICAgICAgICAgICAgdmFyIHJlUmVuZGVyID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICB2YXIgcmVzdWx0ID0gdGVtcGxhdGVCdWlsZGVyLmJ1aWxkKHRlbXBsYXRlLCBzY29wZSk7XG4gICAgICAgICAgICAgIGVsZW1lbnRbMF0uaW5uZXJIVE1MID0gcmVzdWx0O1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmVSZW5kZXIoKTtcblxuICAgICAgICAgICAgdmFyIHdhdGNoRXhwciA9IGF0dHJzLm5nU3RhdGljaXplO1xuXG4gICAgICAgICAgICBpZiAod2F0Y2hFeHByKSB7XG4gICAgICAgICAgICAgIHNjb3BlLiR3YXRjaCh3YXRjaEV4cHIsIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgIHJlUmVuZGVyKCk7XG4gICAgICAgICAgICAgIH0sIHRydWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICB9O1xuICB9XSk7XG5cbm1vZHVsZS5leHBvcnRzID0ge307XG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3NyYy9pbmRleC5qc1xuICoqIG1vZHVsZSBpZCA9IDBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsInZhciB0ZW1wbGF0ZVBhcnNlciA9IFsnJHBhcnNlJywgJyRpbnRlcnBvbGF0ZScsIGZ1bmN0aW9uKCRwYXJzZSwgJGludGVycG9sYXRlKSB7XG4gIHZhciBuZWVkSW50ZXJwb2xhdGUgPSB7ICduZy1zcmMnOiB0cnVlLCAnbmctaHJlZic6IHRydWUgfTtcbiAgdmFyIFRFWFRfTk9ERSA9IDM7XG4gIHZhciBFTEVNRU5UX05PREUgPSAxO1xuXG4gIGZ1bmN0aW9uIHdhbGsoZWwpIHtcbiAgICBpZiAoZWwubm9kZVR5cGUgIT09IEVMRU1FTlRfTk9ERSkgcmV0dXJuO1xuXG4gICAgdmFyIGNoaWxkcmVuID0gW107XG4gICAgdmFyIG5vZGUgPSB7IHRhZ05hbWU6IGVsLnRhZ05hbWUudG9Mb3dlckNhc2UoKSB9O1xuICAgIHZhciBhdHRyaWJ1dGVzID0gZWwuYXR0cmlidXRlcztcbiAgICB2YXIgYXR0cnMsIGRpcnMsIGksIGo7XG5cbiAgICBmb3IgKGkgPSAwLCBqID0gYXR0cmlidXRlcy5sZW5ndGg7IGkgPCBqOyBpKyspIHtcbiAgICAgIHZhciBhdHRyaWJ1dGUgPSBhdHRyaWJ1dGVzW2ldO1xuICAgICAgdmFyIG5hbWUgPSBhdHRyaWJ1dGUubmFtZTtcbiAgICAgIHZhciB2YWx1ZSA9IGF0dHJpYnV0ZS5ub2RlVmFsdWU7XG4gICAgICBpZiAobmFtZSAmJiBuYW1lLnN1YnN0cigwLCAzKSA9PT0gJ25nLScpIHtcbiAgICAgICAgaWYgKCFkaXJzKSB7XG4gICAgICAgICAgZGlycyA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIGlmIChuYW1lLmxlbmd0aCA+IDggJiYgbmFtZS5zdWJzdHIoMCwgOCkgPT09ICduZy1hdHRyLScpIHtcbiAgICAgICAgICBpZiAoIWF0dHJzKSB7XG4gICAgICAgICAgICBhdHRycyA9IHt9O1xuICAgICAgICAgIH1cbiAgICAgICAgICBhdHRyc1tuYW1lLnN1YnN0cig5KV0gPSAkcGFyc2UodmFsdWUpO1xuICAgICAgICB9IGVsc2UgaWYgKG5hbWUgPT09ICduZy1yZXBlYXQnKSB7XG4gICAgICAgICAgdmFyIG1hdGNoZXMgPSAvKFxcdyspXFxzK2luXFxzKyguKj8pJC8uZXhlYyh2YWx1ZSk7XG4gICAgICAgICAgaWYgKG1hdGNoZXMpIHtcbiAgICAgICAgICAgIGRpcnNbbmFtZV0gPSB7XG4gICAgICAgICAgICAgIGl0ZW1OYW1lOiBtYXRjaGVzWzFdLFxuICAgICAgICAgICAgICBnZXRBcnJheTogJHBhcnNlKG1hdGNoZXNbMl0pXG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBkaXJzW25hbWVdID0gbmVlZEludGVycG9sYXRlW25hbWVdID8gJGludGVycG9sYXRlKHZhbHVlKSA6ICRwYXJzZSh2YWx1ZSk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGlmICghYXR0cnMpIHtcbiAgICAgICAgICBhdHRycyA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIGlmICgvXFxzKih7e1xccyooLis/KVxccyp9fSlcXHMqL2dpLnRlc3QodmFsdWUpKSB7XG4gICAgICAgICAgYXR0cnNbbmFtZV0gPSAkaW50ZXJwb2xhdGUodmFsdWUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGF0dHJzW25hbWVdID0gdmFsdWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICB2YXIgY2hpbGROb2RlcyA9IGVsLmNoaWxkTm9kZXM7XG4gICAgZm9yIChpID0gMCwgaiA9IGNoaWxkTm9kZXMubGVuZ3RoOyBpIDwgajsgaSsrKSB7XG4gICAgICB2YXIgY2hpbGQgPSBjaGlsZE5vZGVzW2ldO1xuICAgICAgaWYgKGNoaWxkLm5vZGVUeXBlID09PSBURVhUX05PREUpIHtcbiAgICAgICAgdmFyIHRleHQgPSBjaGlsZC5ub2RlVmFsdWU7XG4gICAgICAgIGlmICh0ZXh0KSB7XG4gICAgICAgICAgaWYgKC9cXHMqKHt7XFxzKiguKz8pXFxzKn19KVxccyovZ2kudGVzdCh0ZXh0KSkge1xuICAgICAgICAgICAgY2hpbGRyZW4ucHVzaCgkaW50ZXJwb2xhdGUodGV4dCkpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0ZXh0ID0gdGV4dC5yZXBsYWNlKC8oXFxyXFxufFxcbnxcXHJ8XFxzKS9nbSwgJycpO1xuICAgICAgICAgICAgaWYgKHRleHQubGVuZ3RoKSB7XG4gICAgICAgICAgICAgIGNoaWxkcmVuLnB1c2godGV4dCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgdmFyIHBhcnNlUmVzdWx0ID0gd2FsayhjaGlsZCk7XG4gICAgICBpZiAocGFyc2VSZXN1bHQpIHtcbiAgICAgICAgY2hpbGRyZW4ucHVzaChwYXJzZVJlc3VsdCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGNoaWxkcmVuLmxlbmd0aCA+IDApIHtcbiAgICAgIG5vZGUuY2hpbGRyZW4gPSBjaGlsZHJlbjtcbiAgICB9XG5cbiAgICBpZiAoYXR0cnMpIHtcbiAgICAgIG5vZGUuYXR0cnMgPSBhdHRycztcbiAgICB9XG5cbiAgICBpZiAoZGlycykge1xuICAgICAgbm9kZS5kaXJzID0gZGlycztcbiAgICB9XG5cbiAgICByZXR1cm4gbm9kZTtcbiAgfVxuXG4gIHJldHVybiB7XG4gICAgcGFyc2U6IGZ1bmN0aW9uKHRlbXBsYXRlKSB7XG4gICAgICByZXR1cm4gd2FsayhyZXF1aXJlKCdkb21pZnknKSh0ZW1wbGF0ZSkpO1xuICAgIH1cbiAgfTtcbn1dO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHRlbXBsYXRlUGFyc2VyO1xuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9zcmMvcGFyc2VyLmpzXG4gKiogbW9kdWxlIGlkID0gMVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiXG4vKipcbiAqIEV4cG9zZSBgcGFyc2VgLlxuICovXG5cbm1vZHVsZS5leHBvcnRzID0gcGFyc2U7XG5cbi8qKlxuICogVGVzdHMgZm9yIGJyb3dzZXIgc3VwcG9ydC5cbiAqL1xuXG52YXIgaW5uZXJIVE1MQnVnID0gZmFsc2U7XG52YXIgYnVnVGVzdERpdjtcbmlmICh0eXBlb2YgZG9jdW1lbnQgIT09ICd1bmRlZmluZWQnKSB7XG4gIGJ1Z1Rlc3REaXYgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTtcbiAgLy8gU2V0dXBcbiAgYnVnVGVzdERpdi5pbm5lckhUTUwgPSAnICA8bGluay8+PHRhYmxlPjwvdGFibGU+PGEgaHJlZj1cIi9hXCI+YTwvYT48aW5wdXQgdHlwZT1cImNoZWNrYm94XCIvPic7XG4gIC8vIE1ha2Ugc3VyZSB0aGF0IGxpbmsgZWxlbWVudHMgZ2V0IHNlcmlhbGl6ZWQgY29ycmVjdGx5IGJ5IGlubmVySFRNTFxuICAvLyBUaGlzIHJlcXVpcmVzIGEgd3JhcHBlciBlbGVtZW50IGluIElFXG4gIGlubmVySFRNTEJ1ZyA9ICFidWdUZXN0RGl2LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdsaW5rJykubGVuZ3RoO1xuICBidWdUZXN0RGl2ID0gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFdyYXAgbWFwIGZyb20ganF1ZXJ5LlxuICovXG5cbnZhciBtYXAgPSB7XG4gIGxlZ2VuZDogWzEsICc8ZmllbGRzZXQ+JywgJzwvZmllbGRzZXQ+J10sXG4gIHRyOiBbMiwgJzx0YWJsZT48dGJvZHk+JywgJzwvdGJvZHk+PC90YWJsZT4nXSxcbiAgY29sOiBbMiwgJzx0YWJsZT48dGJvZHk+PC90Ym9keT48Y29sZ3JvdXA+JywgJzwvY29sZ3JvdXA+PC90YWJsZT4nXSxcbiAgLy8gZm9yIHNjcmlwdC9saW5rL3N0eWxlIHRhZ3MgdG8gd29yayBpbiBJRTYtOCwgeW91IGhhdmUgdG8gd3JhcFxuICAvLyBpbiBhIGRpdiB3aXRoIGEgbm9uLXdoaXRlc3BhY2UgY2hhcmFjdGVyIGluIGZyb250LCBoYSFcbiAgX2RlZmF1bHQ6IGlubmVySFRNTEJ1ZyA/IFsxLCAnWDxkaXY+JywgJzwvZGl2PiddIDogWzAsICcnLCAnJ11cbn07XG5cbm1hcC50ZCA9XG5tYXAudGggPSBbMywgJzx0YWJsZT48dGJvZHk+PHRyPicsICc8L3RyPjwvdGJvZHk+PC90YWJsZT4nXTtcblxubWFwLm9wdGlvbiA9XG5tYXAub3B0Z3JvdXAgPSBbMSwgJzxzZWxlY3QgbXVsdGlwbGU9XCJtdWx0aXBsZVwiPicsICc8L3NlbGVjdD4nXTtcblxubWFwLnRoZWFkID1cbm1hcC50Ym9keSA9XG5tYXAuY29sZ3JvdXAgPVxubWFwLmNhcHRpb24gPVxubWFwLnRmb290ID0gWzEsICc8dGFibGU+JywgJzwvdGFibGU+J107XG5cbm1hcC5wb2x5bGluZSA9XG5tYXAuZWxsaXBzZSA9XG5tYXAucG9seWdvbiA9XG5tYXAuY2lyY2xlID1cbm1hcC50ZXh0ID1cbm1hcC5saW5lID1cbm1hcC5wYXRoID1cbm1hcC5yZWN0ID1cbm1hcC5nID0gWzEsICc8c3ZnIHhtbG5zPVwiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmdcIiB2ZXJzaW9uPVwiMS4xXCI+JywnPC9zdmc+J107XG5cbi8qKlxuICogUGFyc2UgYGh0bWxgIGFuZCByZXR1cm4gYSBET00gTm9kZSBpbnN0YW5jZSwgd2hpY2ggY291bGQgYmUgYSBUZXh0Tm9kZSxcbiAqIEhUTUwgRE9NIE5vZGUgb2Ygc29tZSBraW5kICg8ZGl2PiBmb3IgZXhhbXBsZSksIG9yIGEgRG9jdW1lbnRGcmFnbWVudFxuICogaW5zdGFuY2UsIGRlcGVuZGluZyBvbiB0aGUgY29udGVudHMgb2YgdGhlIGBodG1sYCBzdHJpbmcuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IGh0bWwgLSBIVE1MIHN0cmluZyB0byBcImRvbWlmeVwiXG4gKiBAcGFyYW0ge0RvY3VtZW50fSBkb2MgLSBUaGUgYGRvY3VtZW50YCBpbnN0YW5jZSB0byBjcmVhdGUgdGhlIE5vZGUgZm9yXG4gKiBAcmV0dXJuIHtET01Ob2RlfSB0aGUgVGV4dE5vZGUsIERPTSBOb2RlLCBvciBEb2N1bWVudEZyYWdtZW50IGluc3RhbmNlXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBwYXJzZShodG1sLCBkb2MpIHtcbiAgaWYgKCdzdHJpbmcnICE9IHR5cGVvZiBodG1sKSB0aHJvdyBuZXcgVHlwZUVycm9yKCdTdHJpbmcgZXhwZWN0ZWQnKTtcblxuICAvLyBkZWZhdWx0IHRvIHRoZSBnbG9iYWwgYGRvY3VtZW50YCBvYmplY3RcbiAgaWYgKCFkb2MpIGRvYyA9IGRvY3VtZW50O1xuXG4gIC8vIHRhZyBuYW1lXG4gIHZhciBtID0gLzwoW1xcdzpdKykvLmV4ZWMoaHRtbCk7XG4gIGlmICghbSkgcmV0dXJuIGRvYy5jcmVhdGVUZXh0Tm9kZShodG1sKTtcblxuICBodG1sID0gaHRtbC5yZXBsYWNlKC9eXFxzK3xcXHMrJC9nLCAnJyk7IC8vIFJlbW92ZSBsZWFkaW5nL3RyYWlsaW5nIHdoaXRlc3BhY2VcblxuICB2YXIgdGFnID0gbVsxXTtcblxuICAvLyBib2R5IHN1cHBvcnRcbiAgaWYgKHRhZyA9PSAnYm9keScpIHtcbiAgICB2YXIgZWwgPSBkb2MuY3JlYXRlRWxlbWVudCgnaHRtbCcpO1xuICAgIGVsLmlubmVySFRNTCA9IGh0bWw7XG4gICAgcmV0dXJuIGVsLnJlbW92ZUNoaWxkKGVsLmxhc3RDaGlsZCk7XG4gIH1cblxuICAvLyB3cmFwIG1hcFxuICB2YXIgd3JhcCA9IG1hcFt0YWddIHx8IG1hcC5fZGVmYXVsdDtcbiAgdmFyIGRlcHRoID0gd3JhcFswXTtcbiAgdmFyIHByZWZpeCA9IHdyYXBbMV07XG4gIHZhciBzdWZmaXggPSB3cmFwWzJdO1xuICB2YXIgZWwgPSBkb2MuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gIGVsLmlubmVySFRNTCA9IHByZWZpeCArIGh0bWwgKyBzdWZmaXg7XG4gIHdoaWxlIChkZXB0aC0tKSBlbCA9IGVsLmxhc3RDaGlsZDtcblxuICAvLyBvbmUgZWxlbWVudFxuICBpZiAoZWwuZmlyc3RDaGlsZCA9PSBlbC5sYXN0Q2hpbGQpIHtcbiAgICByZXR1cm4gZWwucmVtb3ZlQ2hpbGQoZWwuZmlyc3RDaGlsZCk7XG4gIH1cblxuICAvLyBzZXZlcmFsIGVsZW1lbnRzXG4gIHZhciBmcmFnbWVudCA9IGRvYy5jcmVhdGVEb2N1bWVudEZyYWdtZW50KCk7XG4gIHdoaWxlIChlbC5maXJzdENoaWxkKSB7XG4gICAgZnJhZ21lbnQuYXBwZW5kQ2hpbGQoZWwucmVtb3ZlQ2hpbGQoZWwuZmlyc3RDaGlsZCkpO1xuICB9XG5cbiAgcmV0dXJuIGZyYWdtZW50O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL34vZG9taWZ5L2luZGV4LmpzXG4gKiogbW9kdWxlIGlkID0gMlxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwidmFyIHRyaW0gPSBmdW5jdGlvbiAoc3RyaW5nKSB7XG4gIHJldHVybiBzdHJpbmcucmVwbGFjZSgvXltcXHNcXHVGRUZGXSt8W1xcc1xcdUZFRkZdKyQvZywgJycpO1xufTtcblxudmFyIHRlbXBsYXRlQnVpbGRlciA9IFtmdW5jdGlvbigpIHtcbiAgZnVuY3Rpb24gc2hhbGxvd0Nsb25lKG9iamVjdCkge1xuICAgIHZhciByZXN1bHQgPSB7fTtcbiAgICBmb3IgKHZhciBwcm9wIGluIG9iamVjdCkge1xuICAgICAgaWYgKG9iamVjdC5oYXNPd25Qcm9wZXJ0eShwcm9wKSkgcmVzdWx0W3Byb3BdID0gb2JqZWN0W3Byb3BdO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbmV3Q29udGV4dChjb250ZXh0KSB7XG4gICAgdmFyIGVtcHR5ID0gZnVuY3Rpb24oKSB7fTtcbiAgICBlbXB0eS5wcm90b3R5cGUgPSBjb250ZXh0O1xuXG4gICAgcmV0dXJuIG5ldyBlbXB0eSgpO1xuICB9XG5cbiAgZnVuY3Rpb24gZ2V0U3R5bGUob2JqZWN0KSB7XG4gICAgdmFyIGNzc1RleHQgPSAnJztcbiAgICBmb3IgKHZhciBwcm9wIGluIG9iamVjdCkge1xuICAgICAgaWYgKG9iamVjdC5oYXNPd25Qcm9wZXJ0eShwcm9wKSkge1xuICAgICAgICBjc3NUZXh0ICs9IHByb3AgKyAnOicgKyBvYmplY3RbcHJvcF0gKyAnOyc7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBjc3NUZXh0O1xuICB9XG5cbiAgZnVuY3Rpb24gZ2V0Q2xhc3NlcyhvYmplY3QpIHtcbiAgICB2YXIgY2xhc3NlcyA9IFtdO1xuICAgIGZvciAodmFyIHByb3AgaW4gb2JqZWN0KSB7XG4gICAgICBpZiAob2JqZWN0Lmhhc093blByb3BlcnR5KHByb3ApICYmICEhb2JqZWN0W3Byb3BdKSB7XG4gICAgICAgIGNsYXNzZXMucHVzaChwcm9wKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGNsYXNzZXMuam9pbignICcpO1xuICB9XG5cbiAgZnVuY3Rpb24gY2xvbmVSZXBlYXROb2RlKG5vZGUpIHtcbiAgICB2YXIgY2xvbmVOb2RlID0gc2hhbGxvd0Nsb25lKG5vZGUpO1xuICAgIGNsb25lTm9kZS5kaXJzID0gc2hhbGxvd0Nsb25lKG5vZGUuZGlycyk7XG4gICAgY2xvbmVOb2RlLmRpcnNbJ25nLXJlcGVhdCddID0gbnVsbDtcbiAgICBkZWxldGUgY2xvbmVOb2RlLmRpcnNbJ25nLXJlcGVhdCddO1xuXG4gICAgcmV0dXJuIGNsb25lTm9kZTtcbiAgfVxuXG4gIHZhciBBVFRSX0RJUl9NQVAgPSB7XG4gICAgJ25nLXNyYyc6IHRydWUsXG4gICAgJ25nLWhyZWYnOiB0cnVlLFxuICAgICduZy1hbHQnOiB0cnVlLFxuICAgICduZy10aXRsZSc6IHRydWUsXG4gICAgJ25nLWlkJzogdHJ1ZSxcbiAgICAnbmctZGlzYWJsZWQnOiB0cnVlLFxuICAgICduZy12YWx1ZSc6IHRydWVcbiAgfTtcblxuICB2YXIgSFRNTF9ESVJfTUFQID0ge1xuICAgICduZy1odG1sJzogdHJ1ZSxcbiAgICAnbmctYmluZCc6IHRydWUsXG4gICAgJ25nLXRleHQnOiB0cnVlXG4gIH07XG5cbiAgdmFyIGVtcHR5QXJyYXkgPSBbXTtcblxuICBmdW5jdGlvbiB0b0hUTUwobm9kZSwgY29udGV4dCkge1xuICAgIHZhciBodG1sLCBpLCBqO1xuICAgIGlmIChub2RlIGluc3RhbmNlb2YgQXJyYXkpIHtcbiAgICAgIGh0bWwgPSAnJztcblxuICAgICAgZm9yIChpID0gMCwgaiA9IG5vZGUubGVuZ3RoOyBpIDwgajsgaSsrKSB7XG4gICAgICAgIGh0bWwgKz0gdG9IVE1MKG5vZGVbaV0sIGNvbnRleHQpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gaHRtbDtcbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIG5vZGUgPT09ICdzdHJpbmcnKSByZXR1cm4gbm9kZTtcbiAgICBpZiAodHlwZW9mIG5vZGUgPT09ICdmdW5jdGlvbicpIHJldHVybiBub2RlKGNvbnRleHQpO1xuXG4gICAgdmFyIHRhZyA9IG5vZGUudGFnTmFtZTtcbiAgICB2YXIgY2hpbGRyZW4gPSBub2RlLmNoaWxkcmVuO1xuICAgIHZhciBhdHRycyA9IG5vZGUuYXR0cnM7XG4gICAgdmFyIGRpcnMgPSBub2RlLmRpcnM7XG4gICAgdmFyIGNvbnRlbnQgPSBub2RlLnRleHRDb250ZW50O1xuICAgIHZhciBjbGFzc2VzID0gJyc7XG4gICAgdmFyIGNzc1RleHQgPSAnJztcblxuICAgIGlmIChkaXJzICYmIGRpcnNbJ25nLXJlcGVhdCddKSB7XG4gICAgICB2YXIgY2xvbmVOb2RlID0gY2xvbmVSZXBlYXROb2RlKG5vZGUpO1xuICAgICAgdmFyIHJlcGVhdERpciA9IGRpcnNbJ25nLXJlcGVhdCddO1xuICAgICAgdmFyIGFycmF5ID0gcmVwZWF0RGlyLmdldEFycmF5KGNvbnRleHQpIHx8IGVtcHR5QXJyYXk7XG4gICAgICB2YXIgaXRlbU5hbWUgPSByZXBlYXREaXIuaXRlbU5hbWU7XG5cbiAgICAgIGh0bWwgPSAnJztcblxuICAgICAgZm9yIChpID0gMCwgaiA9IGFycmF5Lmxlbmd0aDsgaSA8IGo7IGkrKykge1xuICAgICAgICB2YXIgc3ViQ29udGV4dCA9IG5ld0NvbnRleHQoY29udGV4dCk7XG4gICAgICAgIHN1YkNvbnRleHQuJGluZGV4ID0gaTtcbiAgICAgICAgc3ViQ29udGV4dC4kZmlyc3QgPSBpID09PSAwO1xuICAgICAgICBzdWJDb250ZXh0LiRsYXN0ID0gaSA9PT0gaiAtIDE7XG4gICAgICAgIHN1YkNvbnRleHQuJG1pZGRsZSA9ICEoc3ViQ29udGV4dC4kZmlyc3QgfHwgc3ViQ29udGV4dC4kbGFzdCk7XG4gICAgICAgIHN1YkNvbnRleHRbaXRlbU5hbWVdID0gYXJyYXlbaV07XG5cbiAgICAgICAgaHRtbCArPSB0b0hUTUwoY2xvbmVOb2RlLCBzdWJDb250ZXh0KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGh0bWw7XG4gICAgfVxuXG4gICAgaHRtbCA9ICc8JyArIHRhZztcblxuICAgIGZvciAodmFyIGRpciBpbiBkaXJzKSB7XG4gICAgICBpZiAoZGlycy5oYXNPd25Qcm9wZXJ0eShkaXIpKSB7XG4gICAgICAgIHZhciBmbiA9IGRpcnNbZGlyXTtcbiAgICAgICAgdmFyIHZhbHVlO1xuICAgICAgICBpZiAodHlwZW9mIGZuID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgdmFsdWUgPSBmbihjb250ZXh0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChkaXIgPT09ICduZy1pZicpIHtcbiAgICAgICAgICBpZiAoIXZhbHVlKSB7XG4gICAgICAgICAgICByZXR1cm4gJyc7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKEFUVFJfRElSX01BUFtkaXJdKSB7XG4gICAgICAgICAgaHRtbCArPSAnICcgKyBkaXIuc3Vic3RyKDMpICsgJz1cIicgKyB2YWx1ZSArICdcIic7XG4gICAgICAgIH0gZWxzZSBpZiAoSFRNTF9ESVJfTUFQW2Rpcl0pIHtcbiAgICAgICAgICBjb250ZW50ID0gdmFsdWU7XG4gICAgICAgIH0gZWxzZSBpZiAoZGlyID09PSAnbmctc3R5bGUnKSB7XG4gICAgICAgICAgY3NzVGV4dCA9IGdldFN0eWxlKHZhbHVlKSArIGNzc1RleHQ7XG4gICAgICAgIH0gZWxzZSBpZiAoZGlyID09PSAnbmctY2xhc3MnKSB7XG4gICAgICAgICAgY2xhc3NlcyArPSBnZXRDbGFzc2VzKHZhbHVlKTtcbiAgICAgICAgfSBlbHNlIGlmIChkaXIgPT09ICduZy1zaG93JyB8fCBkaXIgPT09ICduZy1oaWRlJykge1xuICAgICAgICAgIGlmICh2YWx1ZSAhPT0gbnVsbCAmJiB2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBjbGFzc2VzICs9ICcgJyArIGRpcjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBmb3IgKHZhciBhdHRyIGluIGF0dHJzKSB7XG4gICAgICBpZiAoYXR0cnMuaGFzT3duUHJvcGVydHkoYXR0cikpIHtcbiAgICAgICAgaWYgKGF0dHIgPT09ICdzdHlsZScpIGNvbnRpbnVlO1xuXG4gICAgICAgIHZhciBhdHRyVmFsdWUgPSBhdHRyc1thdHRyXTtcbiAgICAgICAgaWYgKHR5cGVvZiBhdHRyVmFsdWUgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICBhdHRyVmFsdWUgPSBhdHRyVmFsdWUoY29udGV4dCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGF0dHIgPT09ICdjbGFzcycpIHtcbiAgICAgICAgICBjbGFzc2VzICs9ICcgJyArIGF0dHJWYWx1ZTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBodG1sICs9ICcgJyArIGF0dHIgKyAnPVwiJyArIGF0dHJWYWx1ZSArICdcIic7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoY2xhc3Nlcykge1xuICAgICAgaHRtbCArPSAnIGNsYXNzPVwiJyArIHRyaW0oY2xhc3NlcykgKyAnXCInO1xuICAgIH1cblxuICAgIGlmIChjc3NUZXh0KSB7XG4gICAgICBodG1sICs9ICcgc3R5bGU9XCInICsgY3NzVGV4dCArICdcIic7XG4gICAgfVxuXG4gICAgaHRtbCArPSAnPic7XG5cbiAgICBpZiAoY29udGVudCkge1xuICAgICAgaHRtbCArPSBjb250ZW50O1xuICAgIH1cblxuICAgIGlmIChjaGlsZHJlbikge1xuICAgICAgZm9yIChpID0gMCwgaiA9IGNoaWxkcmVuLmxlbmd0aDsgaSA8IGo7IGkrKykge1xuICAgICAgICB2YXIgY2hpbGQgPSBjaGlsZHJlbltpXTtcbiAgICAgICAgaHRtbCArPSB0b0hUTUwoY2hpbGQsIGNvbnRleHQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGh0bWwgKz0gJzwvJyArIHRhZyArICc+JztcblxuICAgIHJldHVybiBodG1sO1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBidWlsZDogdG9IVE1MXG4gIH07XG59XTtcblxubW9kdWxlLmV4cG9ydHMgPSB0ZW1wbGF0ZUJ1aWxkZXI7XG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3NyYy9idWlsZGVyLmpzXG4gKiogbW9kdWxlIGlkID0gM1xuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ== --------------------------------------------------------------------------------