作者 lihongjuan

2323

  1 +/**
  2 + * html2Json 改造来自: https://github.com/Jxck/html2json
  3 + *
  4 + *
  5 + * author: Di (微信小程序开发工程师)
  6 + * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
  7 + * 垂直微信小程序开发交流社区
  8 + *
  9 + * github地址: https://github.com/icindy/wxParse
  10 + *
  11 + * for: 微信小程序富文本解析
  12 + * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
  13 + */
  14 +
  15 +var __placeImgeUrlHttps = "https";
  16 +var __emojisReg = '';
  17 +var __emojisBaseSrc = '';
  18 +var __emojis = {};
  19 +var wxDiscode = require('./wxDiscode.js');
  20 +var HTMLParser = require('./htmlparser.js');
  21 +// Empty Elements - HTML 5
  22 +var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");
  23 +// Block Elements - HTML 5
  24 +var block = makeMap("br,a,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");
  25 +
  26 +// Inline Elements - HTML 5
  27 +var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
  28 +
  29 +// Elements that you can, intentionally, leave open
  30 +// (and which close themselves)
  31 +var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
  32 +
  33 +// Attributes that have their values filled in disabled="disabled"
  34 +var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
  35 +
  36 +// Special Elements (can contain anything)
  37 +var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block");
  38 +function makeMap(str) {
  39 + var obj = {}, items = str.split(",");
  40 + for (var i = 0; i < items.length; i++)
  41 + obj[items[i]] = true;
  42 + return obj;
  43 +}
  44 +
  45 +function q(v) {
  46 + return '"' + v + '"';
  47 +}
  48 +
  49 +function removeDOCTYPE(html) {
  50 + return html
  51 + .replace(/<\?xml.*\?>\n/, '')
  52 + .replace(/<.*!doctype.*\>\n/, '')
  53 + .replace(/<.*!DOCTYPE.*\>\n/, '');
  54 +}
  55 +
  56 +function trimHtml(html) {
  57 + return html
  58 + // .replace(/\r?\n+/g, '')
  59 + .replace(/<!--.*?-->/ig, '')
  60 + .replace(/\/\*.*?\*\//ig, '')
  61 + .replace(/[ ]+</ig, '<')
  62 +}
  63 +
  64 +
  65 +function html2json(html, bindName) {
  66 + //处理字符串
  67 + html = removeDOCTYPE(html);
  68 + // html = trimHtml(html);
  69 + // html = wxDiscode.strDiscode(html);
  70 + //生成node节点
  71 + var bufArray = [];
  72 + var results = {
  73 + node: bindName,
  74 + nodes: [],
  75 + images:[],
  76 + imageUrls:[]
  77 + };
  78 + var index = 0;
  79 + HTMLParser(html, {
  80 + start: function (tag, attrs, unary) {
  81 + //debug(tag, attrs, unary);
  82 + // node for this element
  83 + var node = {
  84 + node: 'element',
  85 + tag: tag,
  86 + };
  87 +
  88 + if (bufArray.length === 0) {
  89 + node.index = index.toString()
  90 + index += 1
  91 + } else {
  92 + var parent = bufArray[0];
  93 + if (parent.nodes === undefined) {
  94 + parent.nodes = [];
  95 + }
  96 + node.index = parent.index + '.' + parent.nodes.length
  97 + }
  98 +
  99 + if (block[tag]) {
  100 + node.tagType = "block";
  101 + } else if (inline[tag]) {
  102 + node.tagType = "inline";
  103 + } else if (closeSelf[tag]) {
  104 + node.tagType = "closeSelf";
  105 + }
  106 +
  107 + if (attrs.length !== 0) {
  108 + node.attr = attrs.reduce(function (pre, attr) {
  109 + var name = attr.name;
  110 + var value = attr.value;
  111 + if (name == 'class') {
  112 + // console.dir(value);
  113 + // value = value.join("")
  114 + node.classStr = value;
  115 + }
  116 + // has multi attibutes
  117 + // make it array of attribute
  118 + if (name == 'style') {
  119 + // console.dir(value);
  120 + // value = value.join("")
  121 + node.styleStr = value;
  122 + }
  123 + if (value.match(/ /)) {
  124 + value = value.split(' ');
  125 + }
  126 +
  127 +
  128 + // if attr already exists
  129 + // merge it
  130 + if (pre[name]) {
  131 + if (Array.isArray(pre[name])) {
  132 + // already array, push to last
  133 + pre[name].push(value);
  134 + } else {
  135 + // single value, make it array
  136 + pre[name] = [pre[name], value];
  137 + }
  138 + } else {
  139 + // not exist, put it
  140 + pre[name] = value;
  141 + }
  142 +
  143 + return pre;
  144 + }, {});
  145 + }
  146 +
  147 + //对img添加额外数据
  148 + if (node.tag === 'img') {
  149 + node.imgIndex = results.images.length;
  150 + var imgUrl = node.attr.src;
  151 + if (imgUrl[0] == '') {
  152 + imgUrl.splice(0, 1);
  153 + }
  154 + imgUrl = wxDiscode.urlToHttpUrl(imgUrl, __placeImgeUrlHttps);
  155 + node.attr.src = imgUrl;
  156 + node.from = bindName;
  157 + results.images.push(node);
  158 + results.imageUrls.push(imgUrl);
  159 + }
  160 +
  161 + // 处理font标签样式属性
  162 + if (node.tag === 'font') {
  163 + var fontSize = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', '-webkit-xxx-large'];
  164 + var styleAttrs = {
  165 + 'color': 'color',
  166 + 'face': 'font-family',
  167 + 'size': 'font-size'
  168 + };
  169 + if (!node.attr.style) node.attr.style = [];
  170 + if (!node.styleStr) node.styleStr = '';
  171 + for (var key in styleAttrs) {
  172 + if (node.attr[key]) {
  173 + var value = key === 'size' ? fontSize[node.attr[key]-1] : node.attr[key];
  174 + node.attr.style.push(styleAttrs[key]);
  175 + node.attr.style.push(value);
  176 + node.styleStr += styleAttrs[key] + ': ' + value + ';';
  177 + }
  178 + }
  179 + }
  180 +
  181 + //临时记录source资源
  182 + if(node.tag === 'source'){
  183 + results.source = node.attr.src;
  184 + }
  185 +
  186 + if (unary) {
  187 + // if this tag doesn't have end tag
  188 + // like <img src="hoge.png"/>
  189 + // add to parents
  190 + var parent = bufArray[0] || results;
  191 + if (parent.nodes === undefined) {
  192 + parent.nodes = [];
  193 + }
  194 + parent.nodes.push(node);
  195 + } else {
  196 + bufArray.unshift(node);
  197 + }
  198 + },
  199 + end: function (tag) {
  200 + //debug(tag);
  201 + // merge into parent tag
  202 + var node = bufArray.shift();
  203 + if (node.tag !== tag) console.error('invalid state: mismatch end tag');
  204 +
  205 + //当有缓存source资源时于于video补上src资源
  206 + if(node.tag === 'video' && results.source){
  207 + node.attr.src = results.source;
  208 + delete results.source;
  209 + }
  210 +
  211 + if (bufArray.length === 0) {
  212 + results.nodes.push(node);
  213 + } else {
  214 + var parent = bufArray[0];
  215 + if (parent.nodes === undefined) {
  216 + parent.nodes = [];
  217 + }
  218 + parent.nodes.push(node);
  219 + }
  220 + },
  221 + chars: function (text) {
  222 + //debug(text);
  223 + var node = {
  224 + node: 'text',
  225 + text: text,
  226 + textArray:transEmojiStr(text)
  227 + };
  228 +
  229 + if (bufArray.length === 0) {
  230 + node.index = index.toString()
  231 + index += 1
  232 + results.nodes.push(node);
  233 + } else {
  234 + var parent = bufArray[0];
  235 + if (parent.nodes === undefined) {
  236 + parent.nodes = [];
  237 + }
  238 + node.index = parent.index + '.' + parent.nodes.length
  239 + parent.nodes.push(node);
  240 + }
  241 + },
  242 + comment: function (text) {
  243 + //debug(text);
  244 + // var node = {
  245 + // node: 'comment',
  246 + // text: text,
  247 + // };
  248 + // var parent = bufArray[0];
  249 + // if (parent.nodes === undefined) {
  250 + // parent.nodes = [];
  251 + // }
  252 + // parent.nodes.push(node);
  253 + },
  254 + });
  255 + return results;
  256 +};
  257 +
  258 +function transEmojiStr(str){
  259 + // var eReg = new RegExp("["+__reg+' '+"]");
  260 +// str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
  261 +
  262 + var emojiObjs = [];
  263 + //如果正则表达式为空
  264 + if(__emojisReg.length == 0 || !__emojis){
  265 + var emojiObj = {}
  266 + emojiObj.node = "text";
  267 + emojiObj.text = str;
  268 + array = [emojiObj];
  269 + return array;
  270 + }
  271 + //这个地方需要调整
  272 + str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
  273 + var eReg = new RegExp("[:]");
  274 + var array = str.split(eReg);
  275 + for(var i = 0; i < array.length; i++){
  276 + var ele = array[i];
  277 + var emojiObj = {};
  278 + if(__emojis[ele]){
  279 + emojiObj.node = "element";
  280 + emojiObj.tag = "emoji";
  281 + emojiObj.text = __emojis[ele];
  282 + emojiObj.baseSrc= __emojisBaseSrc;
  283 + }else{
  284 + emojiObj.node = "text";
  285 + emojiObj.text = ele;
  286 + }
  287 + emojiObjs.push(emojiObj);
  288 + }
  289 +
  290 + return emojiObjs;
  291 +}
  292 +
  293 +function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){
  294 + __emojisReg = reg;
  295 + __emojisBaseSrc=baseSrc;
  296 + __emojis=emojis;
  297 +}
  298 +
  299 +module.exports = {
  300 + html2json: html2json,
  301 + emojisInit:emojisInit
  302 +};
  303 +
  1 +/**
  2 + *
  3 + * htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
  4 + *
  5 + * author: Di (微信小程序开发工程师)
  6 + * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
  7 + * 垂直微信小程序开发交流社区
  8 + *
  9 + * github地址: https://github.com/icindy/wxParse
  10 + *
  11 + * for: 微信小程序富文本解析
  12 + * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
  13 + */
  14 +// Regular Expressions for parsing tags and attributes
  15 +var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  16 + endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,
  17 + attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
  18 +
  19 +// Empty Elements - HTML 5
  20 +var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");
  21 +
  22 +// Block Elements - HTML 5
  23 +var block = makeMap("a,address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");
  24 +
  25 +// Inline Elements - HTML 5
  26 +var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
  27 +
  28 +// Elements that you can, intentionally, leave open
  29 +// (and which close themselves)
  30 +var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
  31 +
  32 +// Attributes that have their values filled in disabled="disabled"
  33 +var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
  34 +
  35 +// Special Elements (can contain anything)
  36 +var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block");
  37 +
  38 +function HTMLParser(html, handler) {
  39 + var index, chars, match, stack = [], last = html;
  40 + stack.last = function () {
  41 + return this[this.length - 1];
  42 + };
  43 +
  44 + while (html) {
  45 + chars = true;
  46 +
  47 + // Make sure we're not in a script or style element
  48 + if (!stack.last() || !special[stack.last()]) {
  49 +
  50 + // Comment
  51 + if (html.indexOf("<!--") == 0) {
  52 + index = html.indexOf("-->");
  53 +
  54 + if (index >= 0) {
  55 + if (handler.comment)
  56 + handler.comment(html.substring(4, index));
  57 + html = html.substring(index + 3);
  58 + chars = false;
  59 + }
  60 +
  61 + // end tag
  62 + } else if (html.indexOf("</") == 0) {
  63 + match = html.match(endTag);
  64 +
  65 + if (match) {
  66 + html = html.substring(match[0].length);
  67 + match[0].replace(endTag, parseEndTag);
  68 + chars = false;
  69 + }
  70 +
  71 + // start tag
  72 + } else if (html.indexOf("<") == 0) {
  73 + match = html.match(startTag);
  74 +
  75 + if (match) {
  76 + html = html.substring(match[0].length);
  77 + match[0].replace(startTag, parseStartTag);
  78 + chars = false;
  79 + }
  80 + }
  81 +
  82 + if (chars) {
  83 + index = html.indexOf("<");
  84 + var text = ''
  85 + while (index === 0) {
  86 + text += "<";
  87 + html = html.substring(1);
  88 + index = html.indexOf("<");
  89 + }
  90 + text += index < 0 ? html : html.substring(0, index);
  91 + html = index < 0 ? "" : html.substring(index);
  92 +
  93 + if (handler.chars)
  94 + handler.chars(text);
  95 + }
  96 +
  97 + } else {
  98 +
  99 + html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) {
  100 + text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2");
  101 + if (handler.chars)
  102 + handler.chars(text);
  103 +
  104 + return "";
  105 + });
  106 +
  107 +
  108 + parseEndTag("", stack.last());
  109 + }
  110 +
  111 + if (html == last)
  112 + throw "Parse Error: " + html;
  113 + last = html;
  114 + }
  115 +
  116 + // Clean up any remaining tags
  117 + parseEndTag();
  118 +
  119 + function parseStartTag(tag, tagName, rest, unary) {
  120 + tagName = tagName.toLowerCase();
  121 +
  122 + if (block[tagName]) {
  123 + while (stack.last() && inline[stack.last()]) {
  124 + parseEndTag("", stack.last());
  125 + }
  126 + }
  127 +
  128 + if (closeSelf[tagName] && stack.last() == tagName) {
  129 + parseEndTag("", tagName);
  130 + }
  131 +
  132 + unary = empty[tagName] || !!unary;
  133 +
  134 + if (!unary)
  135 + stack.push(tagName);
  136 +
  137 + if (handler.start) {
  138 + var attrs = [];
  139 +
  140 + rest.replace(attr, function (match, name) {
  141 + var value = arguments[2] ? arguments[2] :
  142 + arguments[3] ? arguments[3] :
  143 + arguments[4] ? arguments[4] :
  144 + fillAttrs[name] ? name : "";
  145 +
  146 + attrs.push({
  147 + name: name,
  148 + value: value,
  149 + escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
  150 + });
  151 + });
  152 +
  153 + if (handler.start) {
  154 + handler.start(tagName, attrs, unary);
  155 + }
  156 +
  157 + }
  158 + }
  159 +
  160 + function parseEndTag(tag, tagName) {
  161 + // If no tag name is provided, clean shop
  162 + if (!tagName)
  163 + var pos = 0;
  164 +
  165 + // Find the closest opened tag of the same type
  166 + else {
  167 + tagName = tagName.toLowerCase();
  168 + for (var pos = stack.length - 1; pos >= 0; pos--)
  169 + if (stack[pos] == tagName)
  170 + break;
  171 + }
  172 + if (pos >= 0) {
  173 + // Close all the open elements, up the stack
  174 + for (var i = stack.length - 1; i >= pos; i--)
  175 + if (handler.end)
  176 + handler.end(stack[i]);
  177 +
  178 + // Remove the open elements from the stack
  179 + stack.length = pos;
  180 + }
  181 + }
  182 +};
  183 +
  184 +
  185 +function makeMap(str) {
  186 + var obj = {}, items = str.split(",");
  187 + for (var i = 0; i < items.length; i++)
  188 + obj[items[i]] = true;
  189 + return obj;
  190 +}
  191 +
  192 +module.exports = HTMLParser;
  1 +/**
  2 + *
  3 + * showdown: https://github.com/showdownjs/showdown
  4 + *
  5 + * author: Di (微信小程序开发工程师)
  6 + * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
  7 + * 垂直微信小程序开发交流社区
  8 + *
  9 + * github地址: https://github.com/icindy/wxParse
  10 + *
  11 + * for: 微信小程序富文本解析
  12 + * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
  13 + */
  14 +
  15 +function getDefaultOpts(simple) {
  16 + 'use strict';
  17 +
  18 + var defaultOptions = {
  19 + omitExtraWLInCodeBlocks: {
  20 + defaultValue: false,
  21 + describe: 'Omit the default extra whiteline added to code blocks',
  22 + type: 'boolean'
  23 + },
  24 + noHeaderId: {
  25 + defaultValue: false,
  26 + describe: 'Turn on/off generated header id',
  27 + type: 'boolean'
  28 + },
  29 + prefixHeaderId: {
  30 + defaultValue: false,
  31 + describe: 'Specify a prefix to generated header ids',
  32 + type: 'string'
  33 + },
  34 + headerLevelStart: {
  35 + defaultValue: false,
  36 + describe: 'The header blocks level start',
  37 + type: 'integer'
  38 + },
  39 + parseImgDimensions: {
  40 + defaultValue: false,
  41 + describe: 'Turn on/off image dimension parsing',
  42 + type: 'boolean'
  43 + },
  44 + simplifiedAutoLink: {
  45 + defaultValue: false,
  46 + describe: 'Turn on/off GFM autolink style',
  47 + type: 'boolean'
  48 + },
  49 + literalMidWordUnderscores: {
  50 + defaultValue: false,
  51 + describe: 'Parse midword underscores as literal underscores',
  52 + type: 'boolean'
  53 + },
  54 + strikethrough: {
  55 + defaultValue: false,
  56 + describe: 'Turn on/off strikethrough support',
  57 + type: 'boolean'
  58 + },
  59 + tables: {
  60 + defaultValue: false,
  61 + describe: 'Turn on/off tables support',
  62 + type: 'boolean'
  63 + },
  64 + tablesHeaderId: {
  65 + defaultValue: false,
  66 + describe: 'Add an id to table headers',
  67 + type: 'boolean'
  68 + },
  69 + ghCodeBlocks: {
  70 + defaultValue: true,
  71 + describe: 'Turn on/off GFM fenced code blocks support',
  72 + type: 'boolean'
  73 + },
  74 + tasklists: {
  75 + defaultValue: false,
  76 + describe: 'Turn on/off GFM tasklist support',
  77 + type: 'boolean'
  78 + },
  79 + smoothLivePreview: {
  80 + defaultValue: false,
  81 + describe: 'Prevents weird effects in live previews due to incomplete input',
  82 + type: 'boolean'
  83 + },
  84 + smartIndentationFix: {
  85 + defaultValue: false,
  86 + description: 'Tries to smartly fix identation in es6 strings',
  87 + type: 'boolean'
  88 + }
  89 + };
  90 + if (simple === false) {
  91 + return JSON.parse(JSON.stringify(defaultOptions));
  92 + }
  93 + var ret = {};
  94 + for (var opt in defaultOptions) {
  95 + if (defaultOptions.hasOwnProperty(opt)) {
  96 + ret[opt] = defaultOptions[opt].defaultValue;
  97 + }
  98 + }
  99 + return ret;
  100 +}
  101 +
  102 +/**
  103 + * Created by Tivie on 06-01-2015.
  104 + */
  105 +
  106 +// Private properties
  107 +var showdown = {},
  108 + parsers = {},
  109 + extensions = {},
  110 + globalOptions = getDefaultOpts(true),
  111 + flavor = {
  112 + github: {
  113 + omitExtraWLInCodeBlocks: true,
  114 + prefixHeaderId: 'user-content-',
  115 + simplifiedAutoLink: true,
  116 + literalMidWordUnderscores: true,
  117 + strikethrough: true,
  118 + tables: true,
  119 + tablesHeaderId: true,
  120 + ghCodeBlocks: true,
  121 + tasklists: true
  122 + },
  123 + vanilla: getDefaultOpts(true)
  124 + };
  125 +
  126 +/**
  127 + * helper namespace
  128 + * @type {{}}
  129 + */
  130 +showdown.helper = {};
  131 +
  132 +/**
  133 + * TODO LEGACY SUPPORT CODE
  134 + * @type {{}}
  135 + */
  136 +showdown.extensions = {};
  137 +
  138 +/**
  139 + * Set a global option
  140 + * @static
  141 + * @param {string} key
  142 + * @param {*} value
  143 + * @returns {showdown}
  144 + */
  145 +showdown.setOption = function (key, value) {
  146 + 'use strict';
  147 + globalOptions[key] = value;
  148 + return this;
  149 +};
  150 +
  151 +/**
  152 + * Get a global option
  153 + * @static
  154 + * @param {string} key
  155 + * @returns {*}
  156 + */
  157 +showdown.getOption = function (key) {
  158 + 'use strict';
  159 + return globalOptions[key];
  160 +};
  161 +
  162 +/**
  163 + * Get the global options
  164 + * @static
  165 + * @returns {{}}
  166 + */
  167 +showdown.getOptions = function () {
  168 + 'use strict';
  169 + return globalOptions;
  170 +};
  171 +
  172 +/**
  173 + * Reset global options to the default values
  174 + * @static
  175 + */
  176 +showdown.resetOptions = function () {
  177 + 'use strict';
  178 + globalOptions = getDefaultOpts(true);
  179 +};
  180 +
  181 +/**
  182 + * Set the flavor showdown should use as default
  183 + * @param {string} name
  184 + */
  185 +showdown.setFlavor = function (name) {
  186 + 'use strict';
  187 + if (flavor.hasOwnProperty(name)) {
  188 + var preset = flavor[name];
  189 + for (var option in preset) {
  190 + if (preset.hasOwnProperty(option)) {
  191 + globalOptions[option] = preset[option];
  192 + }
  193 + }
  194 + }
  195 +};
  196 +
  197 +/**
  198 + * Get the default options
  199 + * @static
  200 + * @param {boolean} [simple=true]
  201 + * @returns {{}}
  202 + */
  203 +showdown.getDefaultOptions = function (simple) {
  204 + 'use strict';
  205 + return getDefaultOpts(simple);
  206 +};
  207 +
  208 +/**
  209 + * Get or set a subParser
  210 + *
  211 + * subParser(name) - Get a registered subParser
  212 + * subParser(name, func) - Register a subParser
  213 + * @static
  214 + * @param {string} name
  215 + * @param {function} [func]
  216 + * @returns {*}
  217 + */
  218 +showdown.subParser = function (name, func) {
  219 + 'use strict';
  220 + if (showdown.helper.isString(name)) {
  221 + if (typeof func !== 'undefined') {
  222 + parsers[name] = func;
  223 + } else {
  224 + if (parsers.hasOwnProperty(name)) {
  225 + return parsers[name];
  226 + } else {
  227 + throw Error('SubParser named ' + name + ' not registered!');
  228 + }
  229 + }
  230 + }
  231 +};
  232 +
  233 +/**
  234 + * Gets or registers an extension
  235 + * @static
  236 + * @param {string} name
  237 + * @param {object|function=} ext
  238 + * @returns {*}
  239 + */
  240 +showdown.extension = function (name, ext) {
  241 + 'use strict';
  242 +
  243 + if (!showdown.helper.isString(name)) {
  244 + throw Error('Extension \'name\' must be a string');
  245 + }
  246 +
  247 + name = showdown.helper.stdExtName(name);
  248 +
  249 + // Getter
  250 + if (showdown.helper.isUndefined(ext)) {
  251 + if (!extensions.hasOwnProperty(name)) {
  252 + throw Error('Extension named ' + name + ' is not registered!');
  253 + }
  254 + return extensions[name];
  255 +
  256 + // Setter
  257 + } else {
  258 + // Expand extension if it's wrapped in a function
  259 + if (typeof ext === 'function') {
  260 + ext = ext();
  261 + }
  262 +
  263 + // Ensure extension is an array
  264 + if (!showdown.helper.isArray(ext)) {
  265 + ext = [ext];
  266 + }
  267 +
  268 + var validExtension = validate(ext, name);
  269 +
  270 + if (validExtension.valid) {
  271 + extensions[name] = ext;
  272 + } else {
  273 + throw Error(validExtension.error);
  274 + }
  275 + }
  276 +};
  277 +
  278 +/**
  279 + * Gets all extensions registered
  280 + * @returns {{}}
  281 + */
  282 +showdown.getAllExtensions = function () {
  283 + 'use strict';
  284 + return extensions;
  285 +};
  286 +
  287 +/**
  288 + * Remove an extension
  289 + * @param {string} name
  290 + */
  291 +showdown.removeExtension = function (name) {
  292 + 'use strict';
  293 + delete extensions[name];
  294 +};
  295 +
  296 +/**
  297 + * Removes all extensions
  298 + */
  299 +showdown.resetExtensions = function () {
  300 + 'use strict';
  301 + extensions = {};
  302 +};
  303 +
  304 +/**
  305 + * Validate extension
  306 + * @param {array} extension
  307 + * @param {string} name
  308 + * @returns {{valid: boolean, error: string}}
  309 + */
  310 +function validate(extension, name) {
  311 + 'use strict';
  312 +
  313 + var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
  314 + ret = {
  315 + valid: true,
  316 + error: ''
  317 + };
  318 +
  319 + if (!showdown.helper.isArray(extension)) {
  320 + extension = [extension];
  321 + }
  322 +
  323 + for (var i = 0; i < extension.length; ++i) {
  324 + var baseMsg = errMsg + ' sub-extension ' + i + ': ',
  325 + ext = extension[i];
  326 + if (typeof ext !== 'object') {
  327 + ret.valid = false;
  328 + ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
  329 + return ret;
  330 + }
  331 +
  332 + if (!showdown.helper.isString(ext.type)) {
  333 + ret.valid = false;
  334 + ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
  335 + return ret;
  336 + }
  337 +
  338 + var type = ext.type = ext.type.toLowerCase();
  339 +
  340 + // normalize extension type
  341 + if (type === 'language') {
  342 + type = ext.type = 'lang';
  343 + }
  344 +
  345 + if (type === 'html') {
  346 + type = ext.type = 'output';
  347 + }
  348 +
  349 + if (type !== 'lang' && type !== 'output' && type !== 'listener') {
  350 + ret.valid = false;
  351 + ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
  352 + return ret;
  353 + }
  354 +
  355 + if (type === 'listener') {
  356 + if (showdown.helper.isUndefined(ext.listeners)) {
  357 + ret.valid = false;
  358 + ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
  359 + return ret;
  360 + }
  361 + } else {
  362 + if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
  363 + ret.valid = false;
  364 + ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
  365 + return ret;
  366 + }
  367 + }
  368 +
  369 + if (ext.listeners) {
  370 + if (typeof ext.listeners !== 'object') {
  371 + ret.valid = false;
  372 + ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
  373 + return ret;
  374 + }
  375 + for (var ln in ext.listeners) {
  376 + if (ext.listeners.hasOwnProperty(ln)) {
  377 + if (typeof ext.listeners[ln] !== 'function') {
  378 + ret.valid = false;
  379 + ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
  380 + ' must be a function but ' + typeof ext.listeners[ln] + ' given';
  381 + return ret;
  382 + }
  383 + }
  384 + }
  385 + }
  386 +
  387 + if (ext.filter) {
  388 + if (typeof ext.filter !== 'function') {
  389 + ret.valid = false;
  390 + ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
  391 + return ret;
  392 + }
  393 + } else if (ext.regex) {
  394 + if (showdown.helper.isString(ext.regex)) {
  395 + ext.regex = new RegExp(ext.regex, 'g');
  396 + }
  397 + if (!ext.regex instanceof RegExp) {
  398 + ret.valid = false;
  399 + ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
  400 + return ret;
  401 + }
  402 + if (showdown.helper.isUndefined(ext.replace)) {
  403 + ret.valid = false;
  404 + ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
  405 + return ret;
  406 + }
  407 + }
  408 + }
  409 + return ret;
  410 +}
  411 +
  412 +/**
  413 + * Validate extension
  414 + * @param {object} ext
  415 + * @returns {boolean}
  416 + */
  417 +showdown.validateExtension = function (ext) {
  418 + 'use strict';
  419 +
  420 + var validateExtension = validate(ext, null);
  421 + if (!validateExtension.valid) {
  422 + console.warn(validateExtension.error);
  423 + return false;
  424 + }
  425 + return true;
  426 +};
  427 +
  428 +/**
  429 + * showdownjs helper functions
  430 + */
  431 +
  432 +if (!showdown.hasOwnProperty('helper')) {
  433 + showdown.helper = {};
  434 +}
  435 +
  436 +/**
  437 + * Check if var is string
  438 + * @static
  439 + * @param {string} a
  440 + * @returns {boolean}
  441 + */
  442 +showdown.helper.isString = function isString(a) {
  443 + 'use strict';
  444 + return (typeof a === 'string' || a instanceof String);
  445 +};
  446 +
  447 +/**
  448 + * Check if var is a function
  449 + * @static
  450 + * @param {string} a
  451 + * @returns {boolean}
  452 + */
  453 +showdown.helper.isFunction = function isFunction(a) {
  454 + 'use strict';
  455 + var getType = {};
  456 + return a && getType.toString.call(a) === '[object Function]';
  457 +};
  458 +
  459 +/**
  460 + * ForEach helper function
  461 + * @static
  462 + * @param {*} obj
  463 + * @param {function} callback
  464 + */
  465 +showdown.helper.forEach = function forEach(obj, callback) {
  466 + 'use strict';
  467 + if (typeof obj.forEach === 'function') {
  468 + obj.forEach(callback);
  469 + } else {
  470 + for (var i = 0; i < obj.length; i++) {
  471 + callback(obj[i], i, obj);
  472 + }
  473 + }
  474 +};
  475 +
  476 +/**
  477 + * isArray helper function
  478 + * @static
  479 + * @param {*} a
  480 + * @returns {boolean}
  481 + */
  482 +showdown.helper.isArray = function isArray(a) {
  483 + 'use strict';
  484 + return a.constructor === Array;
  485 +};
  486 +
  487 +/**
  488 + * Check if value is undefined
  489 + * @static
  490 + * @param {*} value The value to check.
  491 + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
  492 + */
  493 +showdown.helper.isUndefined = function isUndefined(value) {
  494 + 'use strict';
  495 + return typeof value === 'undefined';
  496 +};
  497 +
  498 +/**
  499 + * Standardidize extension name
  500 + * @static
  501 + * @param {string} s extension name
  502 + * @returns {string}
  503 + */
  504 +showdown.helper.stdExtName = function (s) {
  505 + 'use strict';
  506 + return s.replace(/[_-]||\s/g, '').toLowerCase();
  507 +};
  508 +
  509 +function escapeCharactersCallback(wholeMatch, m1) {
  510 + 'use strict';
  511 + var charCodeToEscape = m1.charCodeAt(0);
  512 + return '~E' + charCodeToEscape + 'E';
  513 +}
  514 +
  515 +/**
  516 + * Callback used to escape characters when passing through String.replace
  517 + * @static
  518 + * @param {string} wholeMatch
  519 + * @param {string} m1
  520 + * @returns {string}
  521 + */
  522 +showdown.helper.escapeCharactersCallback = escapeCharactersCallback;
  523 +
  524 +/**
  525 + * Escape characters in a string
  526 + * @static
  527 + * @param {string} text
  528 + * @param {string} charsToEscape
  529 + * @param {boolean} afterBackslash
  530 + * @returns {XML|string|void|*}
  531 + */
  532 +showdown.helper.escapeCharacters = function escapeCharacters(text, charsToEscape, afterBackslash) {
  533 + 'use strict';
  534 + // First we have to escape the escape characters so that
  535 + // we can build a character class out of them
  536 + var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])';
  537 +
  538 + if (afterBackslash) {
  539 + regexString = '\\\\' + regexString;
  540 + }
  541 +
  542 + var regex = new RegExp(regexString, 'g');
  543 + text = text.replace(regex, escapeCharactersCallback);
  544 +
  545 + return text;
  546 +};
  547 +
  548 +var rgxFindMatchPos = function (str, left, right, flags) {
  549 + 'use strict';
  550 + var f = flags || '',
  551 + g = f.indexOf('g') > -1,
  552 + x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')),
  553 + l = new RegExp(left, f.replace(/g/g, '')),
  554 + pos = [],
  555 + t, s, m, start, end;
  556 +
  557 + do {
  558 + t = 0;
  559 + while ((m = x.exec(str))) {
  560 + if (l.test(m[0])) {
  561 + if (!(t++)) {
  562 + s = x.lastIndex;
  563 + start = s - m[0].length;
  564 + }
  565 + } else if (t) {
  566 + if (!--t) {
  567 + end = m.index + m[0].length;
  568 + var obj = {
  569 + left: {start: start, end: s},
  570 + match: {start: s, end: m.index},
  571 + right: {start: m.index, end: end},
  572 + wholeMatch: {start: start, end: end}
  573 + };
  574 + pos.push(obj);
  575 + if (!g) {
  576 + return pos;
  577 + }
  578 + }
  579 + }
  580 + }
  581 + } while (t && (x.lastIndex = s));
  582 +
  583 + return pos;
  584 +};
  585 +
  586 +/**
  587 + * matchRecursiveRegExp
  588 + *
  589 + * (c) 2007 Steven Levithan <stevenlevithan.com>
  590 + * MIT License
  591 + *
  592 + * Accepts a string to search, a left and right format delimiter
  593 + * as regex patterns, and optional regex flags. Returns an array
  594 + * of matches, allowing nested instances of left/right delimiters.
  595 + * Use the "g" flag to return all matches, otherwise only the
  596 + * first is returned. Be careful to ensure that the left and
  597 + * right format delimiters produce mutually exclusive matches.
  598 + * Backreferences are not supported within the right delimiter
  599 + * due to how it is internally combined with the left delimiter.
  600 + * When matching strings whose format delimiters are unbalanced
  601 + * to the left or right, the output is intentionally as a
  602 + * conventional regex library with recursion support would
  603 + * produce, e.g. "<<x>" and "<x>>" both produce ["x"] when using
  604 + * "<" and ">" as the delimiters (both strings contain a single,
  605 + * balanced instance of "<x>").
  606 + *
  607 + * examples:
  608 + * matchRecursiveRegExp("test", "\\(", "\\)")
  609 + * returns: []
  610 + * matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
  611 + * returns: ["t<<e>><s>", ""]
  612 + * matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
  613 + * returns: ["test"]
  614 + */
  615 +showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {
  616 + 'use strict';
  617 +
  618 + var matchPos = rgxFindMatchPos (str, left, right, flags),
  619 + results = [];
  620 +
  621 + for (var i = 0; i < matchPos.length; ++i) {
  622 + results.push([
  623 + str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
  624 + str.slice(matchPos[i].match.start, matchPos[i].match.end),
  625 + str.slice(matchPos[i].left.start, matchPos[i].left.end),
  626 + str.slice(matchPos[i].right.start, matchPos[i].right.end)
  627 + ]);
  628 + }
  629 + return results;
  630 +};
  631 +
  632 +/**
  633 + *
  634 + * @param {string} str
  635 + * @param {string|function} replacement
  636 + * @param {string} left
  637 + * @param {string} right
  638 + * @param {string} flags
  639 + * @returns {string}
  640 + */
  641 +showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {
  642 + 'use strict';
  643 +
  644 + if (!showdown.helper.isFunction(replacement)) {
  645 + var repStr = replacement;
  646 + replacement = function () {
  647 + return repStr;
  648 + };
  649 + }
  650 +
  651 + var matchPos = rgxFindMatchPos(str, left, right, flags),
  652 + finalStr = str,
  653 + lng = matchPos.length;
  654 +
  655 + if (lng > 0) {
  656 + var bits = [];
  657 + if (matchPos[0].wholeMatch.start !== 0) {
  658 + bits.push(str.slice(0, matchPos[0].wholeMatch.start));
  659 + }
  660 + for (var i = 0; i < lng; ++i) {
  661 + bits.push(
  662 + replacement(
  663 + str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
  664 + str.slice(matchPos[i].match.start, matchPos[i].match.end),
  665 + str.slice(matchPos[i].left.start, matchPos[i].left.end),
  666 + str.slice(matchPos[i].right.start, matchPos[i].right.end)
  667 + )
  668 + );
  669 + if (i < lng - 1) {
  670 + bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));
  671 + }
  672 + }
  673 + if (matchPos[lng - 1].wholeMatch.end < str.length) {
  674 + bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));
  675 + }
  676 + finalStr = bits.join('');
  677 + }
  678 + return finalStr;
  679 +};
  680 +
  681 +/**
  682 + * POLYFILLS
  683 + */
  684 +if (showdown.helper.isUndefined(console)) {
  685 + console = {
  686 + warn: function (msg) {
  687 + 'use strict';
  688 + alert(msg);
  689 + },
  690 + log: function (msg) {
  691 + 'use strict';
  692 + alert(msg);
  693 + },
  694 + error: function (msg) {
  695 + 'use strict';
  696 + throw msg;
  697 + }
  698 + };
  699 +}
  700 +
  701 +/**
  702 + * Created by Estevao on 31-05-2015.
  703 + */
  704 +
  705 +/**
  706 + * Showdown Converter class
  707 + * @class
  708 + * @param {object} [converterOptions]
  709 + * @returns {Converter}
  710 + */
  711 +showdown.Converter = function (converterOptions) {
  712 + 'use strict';
  713 +
  714 + var
  715 + /**
  716 + * Options used by this converter
  717 + * @private
  718 + * @type {{}}
  719 + */
  720 + options = {},
  721 +
  722 + /**
  723 + * Language extensions used by this converter
  724 + * @private
  725 + * @type {Array}
  726 + */
  727 + langExtensions = [],
  728 +
  729 + /**
  730 + * Output modifiers extensions used by this converter
  731 + * @private
  732 + * @type {Array}
  733 + */
  734 + outputModifiers = [],
  735 +
  736 + /**
  737 + * Event listeners
  738 + * @private
  739 + * @type {{}}
  740 + */
  741 + listeners = {};
  742 +
  743 + _constructor();
  744 +
  745 + /**
  746 + * Converter constructor
  747 + * @private
  748 + */
  749 + function _constructor() {
  750 + converterOptions = converterOptions || {};
  751 +
  752 + for (var gOpt in globalOptions) {
  753 + if (globalOptions.hasOwnProperty(gOpt)) {
  754 + options[gOpt] = globalOptions[gOpt];
  755 + }
  756 + }
  757 +
  758 + // Merge options
  759 + if (typeof converterOptions === 'object') {
  760 + for (var opt in converterOptions) {
  761 + if (converterOptions.hasOwnProperty(opt)) {
  762 + options[opt] = converterOptions[opt];
  763 + }
  764 + }
  765 + } else {
  766 + throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions +
  767 + ' was passed instead.');
  768 + }
  769 +
  770 + if (options.extensions) {
  771 + showdown.helper.forEach(options.extensions, _parseExtension);
  772 + }
  773 + }
  774 +
  775 + /**
  776 + * Parse extension
  777 + * @param {*} ext
  778 + * @param {string} [name='']
  779 + * @private
  780 + */
  781 + function _parseExtension(ext, name) {
  782 +
  783 + name = name || null;
  784 + // If it's a string, the extension was previously loaded
  785 + if (showdown.helper.isString(ext)) {
  786 + ext = showdown.helper.stdExtName(ext);
  787 + name = ext;
  788 +
  789 + // LEGACY_SUPPORT CODE
  790 + if (showdown.extensions[ext]) {
  791 + console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' +
  792 + 'Please inform the developer that the extension should be updated!');
  793 + legacyExtensionLoading(showdown.extensions[ext], ext);
  794 + return;
  795 + // END LEGACY SUPPORT CODE
  796 +
  797 + } else if (!showdown.helper.isUndefined(extensions[ext])) {
  798 + ext = extensions[ext];
  799 +
  800 + } else {
  801 + throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.');
  802 + }
  803 + }
  804 +
  805 + if (typeof ext === 'function') {
  806 + ext = ext();
  807 + }
  808 +
  809 + if (!showdown.helper.isArray(ext)) {
  810 + ext = [ext];
  811 + }
  812 +
  813 + var validExt = validate(ext, name);
  814 + if (!validExt.valid) {
  815 + throw Error(validExt.error);
  816 + }
  817 +
  818 + for (var i = 0; i < ext.length; ++i) {
  819 + switch (ext[i].type) {
  820 +
  821 + case 'lang':
  822 + langExtensions.push(ext[i]);
  823 + break;
  824 +
  825 + case 'output':
  826 + outputModifiers.push(ext[i]);
  827 + break;
  828 + }
  829 + if (ext[i].hasOwnProperty(listeners)) {
  830 + for (var ln in ext[i].listeners) {
  831 + if (ext[i].listeners.hasOwnProperty(ln)) {
  832 + listen(ln, ext[i].listeners[ln]);
  833 + }
  834 + }
  835 + }
  836 + }
  837 +
  838 + }
  839 +
  840 + /**
  841 + * LEGACY_SUPPORT
  842 + * @param {*} ext
  843 + * @param {string} name
  844 + */
  845 + function legacyExtensionLoading(ext, name) {
  846 + if (typeof ext === 'function') {
  847 + ext = ext(new showdown.Converter());
  848 + }
  849 + if (!showdown.helper.isArray(ext)) {
  850 + ext = [ext];
  851 + }
  852 + var valid = validate(ext, name);
  853 +
  854 + if (!valid.valid) {
  855 + throw Error(valid.error);
  856 + }
  857 +
  858 + for (var i = 0; i < ext.length; ++i) {
  859 + switch (ext[i].type) {
  860 + case 'lang':
  861 + langExtensions.push(ext[i]);
  862 + break;
  863 + case 'output':
  864 + outputModifiers.push(ext[i]);
  865 + break;
  866 + default:// should never reach here
  867 + throw Error('Extension loader error: Type unrecognized!!!');
  868 + }
  869 + }
  870 + }
  871 +
  872 + /**
  873 + * Listen to an event
  874 + * @param {string} name
  875 + * @param {function} callback
  876 + */
  877 + function listen(name, callback) {
  878 + if (!showdown.helper.isString(name)) {
  879 + throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given');
  880 + }
  881 +
  882 + if (typeof callback !== 'function') {
  883 + throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given');
  884 + }
  885 +
  886 + if (!listeners.hasOwnProperty(name)) {
  887 + listeners[name] = [];
  888 + }
  889 + listeners[name].push(callback);
  890 + }
  891 +
  892 + function rTrimInputText(text) {
  893 + var rsp = text.match(/^\s*/)[0].length,
  894 + rgx = new RegExp('^\\s{0,' + rsp + '}', 'gm');
  895 + return text.replace(rgx, '');
  896 + }
  897 +
  898 + /**
  899 + * Dispatch an event
  900 + * @private
  901 + * @param {string} evtName Event name
  902 + * @param {string} text Text
  903 + * @param {{}} options Converter Options
  904 + * @param {{}} globals
  905 + * @returns {string}
  906 + */
  907 + this._dispatch = function dispatch (evtName, text, options, globals) {
  908 + if (listeners.hasOwnProperty(evtName)) {
  909 + for (var ei = 0; ei < listeners[evtName].length; ++ei) {
  910 + var nText = listeners[evtName][ei](evtName, text, this, options, globals);
  911 + if (nText && typeof nText !== 'undefined') {
  912 + text = nText;
  913 + }
  914 + }
  915 + }
  916 + return text;
  917 + };
  918 +
  919 + /**
  920 + * Listen to an event
  921 + * @param {string} name
  922 + * @param {function} callback
  923 + * @returns {showdown.Converter}
  924 + */
  925 + this.listen = function (name, callback) {
  926 + listen(name, callback);
  927 + return this;
  928 + };
  929 +
  930 + /**
  931 + * Converts a markdown string into HTML
  932 + * @param {string} text
  933 + * @returns {*}
  934 + */
  935 + this.makeHtml = function (text) {
  936 + //check if text is not falsy
  937 + if (!text) {
  938 + return text;
  939 + }
  940 +
  941 + var globals = {
  942 + gHtmlBlocks: [],
  943 + gHtmlMdBlocks: [],
  944 + gHtmlSpans: [],
  945 + gUrls: {},
  946 + gTitles: {},
  947 + gDimensions: {},
  948 + gListLevel: 0,
  949 + hashLinkCounts: {},
  950 + langExtensions: langExtensions,
  951 + outputModifiers: outputModifiers,
  952 + converter: this,
  953 + ghCodeBlocks: []
  954 + };
  955 +
  956 + // attacklab: Replace ~ with ~T
  957 + // This lets us use tilde as an escape char to avoid md5 hashes
  958 + // The choice of character is arbitrary; anything that isn't
  959 + // magic in Markdown will work.
  960 + text = text.replace(/~/g, '~T');
  961 +
  962 + // attacklab: Replace $ with ~D
  963 + // RegExp interprets $ as a special character
  964 + // when it's in a replacement string
  965 + text = text.replace(/\$/g, '~D');
  966 +
  967 + // Standardize line endings
  968 + text = text.replace(/\r\n/g, '\n'); // DOS to Unix
  969 + text = text.replace(/\r/g, '\n'); // Mac to Unix
  970 +
  971 + if (options.smartIndentationFix) {
  972 + text = rTrimInputText(text);
  973 + }
  974 +
  975 + // Make sure text begins and ends with a couple of newlines:
  976 + //text = '\n\n' + text + '\n\n';
  977 + text = text;
  978 + // detab
  979 + text = showdown.subParser('detab')(text, options, globals);
  980 +
  981 + // stripBlankLines
  982 + text = showdown.subParser('stripBlankLines')(text, options, globals);
  983 +
  984 + //run languageExtensions
  985 + showdown.helper.forEach(langExtensions, function (ext) {
  986 + text = showdown.subParser('runExtension')(ext, text, options, globals);
  987 + });
  988 +
  989 + // run the sub parsers
  990 + text = showdown.subParser('hashPreCodeTags')(text, options, globals);
  991 + text = showdown.subParser('githubCodeBlocks')(text, options, globals);
  992 + text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
  993 + text = showdown.subParser('hashHTMLSpans')(text, options, globals);
  994 + text = showdown.subParser('stripLinkDefinitions')(text, options, globals);
  995 + text = showdown.subParser('blockGamut')(text, options, globals);
  996 + text = showdown.subParser('unhashHTMLSpans')(text, options, globals);
  997 + text = showdown.subParser('unescapeSpecialChars')(text, options, globals);
  998 +
  999 + // attacklab: Restore dollar signs
  1000 + text = text.replace(/~D/g, '$$');
  1001 +
  1002 + // attacklab: Restore tildes
  1003 + text = text.replace(/~T/g, '~');
  1004 +
  1005 + // Run output modifiers
  1006 + showdown.helper.forEach(outputModifiers, function (ext) {
  1007 + text = showdown.subParser('runExtension')(ext, text, options, globals);
  1008 + });
  1009 + return text;
  1010 + };
  1011 +
  1012 + /**
  1013 + * Set an option of this Converter instance
  1014 + * @param {string} key
  1015 + * @param {*} value
  1016 + */
  1017 + this.setOption = function (key, value) {
  1018 + options[key] = value;
  1019 + };
  1020 +
  1021 + /**
  1022 + * Get the option of this Converter instance
  1023 + * @param {string} key
  1024 + * @returns {*}
  1025 + */
  1026 + this.getOption = function (key) {
  1027 + return options[key];
  1028 + };
  1029 +
  1030 + /**
  1031 + * Get the options of this Converter instance
  1032 + * @returns {{}}
  1033 + */
  1034 + this.getOptions = function () {
  1035 + return options;
  1036 + };
  1037 +
  1038 + /**
  1039 + * Add extension to THIS converter
  1040 + * @param {{}} extension
  1041 + * @param {string} [name=null]
  1042 + */
  1043 + this.addExtension = function (extension, name) {
  1044 + name = name || null;
  1045 + _parseExtension(extension, name);
  1046 + };
  1047 +
  1048 + /**
  1049 + * Use a global registered extension with THIS converter
  1050 + * @param {string} extensionName Name of the previously registered extension
  1051 + */
  1052 + this.useExtension = function (extensionName) {
  1053 + _parseExtension(extensionName);
  1054 + };
  1055 +
  1056 + /**
  1057 + * Set the flavor THIS converter should use
  1058 + * @param {string} name
  1059 + */
  1060 + this.setFlavor = function (name) {
  1061 + if (flavor.hasOwnProperty(name)) {
  1062 + var preset = flavor[name];
  1063 + for (var option in preset) {
  1064 + if (preset.hasOwnProperty(option)) {
  1065 + options[option] = preset[option];
  1066 + }
  1067 + }
  1068 + }
  1069 + };
  1070 +
  1071 + /**
  1072 + * Remove an extension from THIS converter.
  1073 + * Note: This is a costly operation. It's better to initialize a new converter
  1074 + * and specify the extensions you wish to use
  1075 + * @param {Array} extension
  1076 + */
  1077 + this.removeExtension = function (extension) {
  1078 + if (!showdown.helper.isArray(extension)) {
  1079 + extension = [extension];
  1080 + }
  1081 + for (var a = 0; a < extension.length; ++a) {
  1082 + var ext = extension[a];
  1083 + for (var i = 0; i < langExtensions.length; ++i) {
  1084 + if (langExtensions[i] === ext) {
  1085 + langExtensions[i].splice(i, 1);
  1086 + }
  1087 + }
  1088 + for (var ii = 0; ii < outputModifiers.length; ++i) {
  1089 + if (outputModifiers[ii] === ext) {
  1090 + outputModifiers[ii].splice(i, 1);
  1091 + }
  1092 + }
  1093 + }
  1094 + };
  1095 +
  1096 + /**
  1097 + * Get all extension of THIS converter
  1098 + * @returns {{language: Array, output: Array}}
  1099 + */
  1100 + this.getAllExtensions = function () {
  1101 + return {
  1102 + language: langExtensions,
  1103 + output: outputModifiers
  1104 + };
  1105 + };
  1106 +};
  1107 +
  1108 +/**
  1109 + * Turn Markdown link shortcuts into XHTML <a> tags.
  1110 + */
  1111 +showdown.subParser('anchors', function (text, options, globals) {
  1112 + 'use strict';
  1113 +
  1114 + text = globals.converter._dispatch('anchors.before', text, options, globals);
  1115 +
  1116 + var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
  1117 + if (showdown.helper.isUndefined(m7)) {
  1118 + m7 = '';
  1119 + }
  1120 + wholeMatch = m1;
  1121 + var linkText = m2,
  1122 + linkId = m3.toLowerCase(),
  1123 + url = m4,
  1124 + title = m7;
  1125 +
  1126 + if (!url) {
  1127 + if (!linkId) {
  1128 + // lower-case and turn embedded newlines into spaces
  1129 + linkId = linkText.toLowerCase().replace(/ ?\n/g, ' ');
  1130 + }
  1131 + url = '#' + linkId;
  1132 +
  1133 + if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {
  1134 + url = globals.gUrls[linkId];
  1135 + if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {
  1136 + title = globals.gTitles[linkId];
  1137 + }
  1138 + } else {
  1139 + if (wholeMatch.search(/\(\s*\)$/m) > -1) {
  1140 + // Special case for explicit empty url
  1141 + url = '';
  1142 + } else {
  1143 + return wholeMatch;
  1144 + }
  1145 + }
  1146 + }
  1147 +
  1148 + url = showdown.helper.escapeCharacters(url, '*_', false);
  1149 + var result = '<a href="' + url + '"';
  1150 +
  1151 + if (title !== '' && title !== null) {
  1152 + title = title.replace(/"/g, '&quot;');
  1153 + title = showdown.helper.escapeCharacters(title, '*_', false);
  1154 + result += ' title="' + title + '"';
  1155 + }
  1156 +
  1157 + result += '>' + linkText + '</a>';
  1158 +
  1159 + return result;
  1160 + };
  1161 +
  1162 + // First, handle reference-style links: [link text] [id]
  1163 + /*
  1164 + text = text.replace(/
  1165 + ( // wrap whole match in $1
  1166 + \[
  1167 + (
  1168 + (?:
  1169 + \[[^\]]*\] // allow brackets nested one level
  1170 + |
  1171 + [^\[] // or anything else
  1172 + )*
  1173 + )
  1174 + \]
  1175 +
  1176 + [ ]? // one optional space
  1177 + (?:\n[ ]*)? // one optional newline followed by spaces
  1178 +
  1179 + \[
  1180 + (.*?) // id = $3
  1181 + \]
  1182 + )()()()() // pad remaining backreferences
  1183 + /g,_DoAnchors_callback);
  1184 + */
  1185 + text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g, writeAnchorTag);
  1186 +
  1187 + //
  1188 + // Next, inline-style links: [link text](url "optional title")
  1189 + //
  1190 +
  1191 + /*
  1192 + text = text.replace(/
  1193 + ( // wrap whole match in $1
  1194 + \[
  1195 + (
  1196 + (?:
  1197 + \[[^\]]*\] // allow brackets nested one level
  1198 + |
  1199 + [^\[\]] // or anything else
  1200 + )
  1201 + )
  1202 + \]
  1203 + \( // literal paren
  1204 + [ \t]*
  1205 + () // no id, so leave $3 empty
  1206 + <?(.*?)>? // href = $4
  1207 + [ \t]*
  1208 + ( // $5
  1209 + (['"]) // quote char = $6
  1210 + (.*?) // Title = $7
  1211 + \6 // matching quote
  1212 + [ \t]* // ignore any spaces/tabs between closing quote and )
  1213 + )? // title is optional
  1214 + \)
  1215 + )
  1216 + /g,writeAnchorTag);
  1217 + */
  1218 + text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
  1219 + writeAnchorTag);
  1220 +
  1221 + //
  1222 + // Last, handle reference-style shortcuts: [link text]
  1223 + // These must come last in case you've also got [link test][1]
  1224 + // or [link test](/foo)
  1225 + //
  1226 +
  1227 + /*
  1228 + text = text.replace(/
  1229 + ( // wrap whole match in $1
  1230 + \[
  1231 + ([^\[\]]+) // link text = $2; can't contain '[' or ']'
  1232 + \]
  1233 + )()()()()() // pad rest of backreferences
  1234 + /g, writeAnchorTag);
  1235 + */
  1236 + text = text.replace(/(\[([^\[\]]+)])()()()()()/g, writeAnchorTag);
  1237 +
  1238 + text = globals.converter._dispatch('anchors.after', text, options, globals);
  1239 + return text;
  1240 +});
  1241 +
  1242 +showdown.subParser('autoLinks', function (text, options, globals) {
  1243 + 'use strict';
  1244 +
  1245 + text = globals.converter._dispatch('autoLinks.before', text, options, globals);
  1246 +
  1247 + var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi,
  1248 + delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,
  1249 + simpleMailRegex = /(?:^|[ \n\t])([A-Za-z0-9!#$%&'*+-/=?^_`\{|}~\.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?:$|[ \n\t])/gi,
  1250 + delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;
  1251 +
  1252 + text = text.replace(delimUrlRegex, replaceLink);
  1253 + text = text.replace(delimMailRegex, replaceMail);
  1254 + // simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
  1255 + // Email addresses: <address@domain.foo>
  1256 +
  1257 + if (options.simplifiedAutoLink) {
  1258 + text = text.replace(simpleURLRegex, replaceLink);
  1259 + text = text.replace(simpleMailRegex, replaceMail);
  1260 + }
  1261 +
  1262 + function replaceLink(wm, link) {
  1263 + var lnkTxt = link;
  1264 + if (/^www\./i.test(link)) {
  1265 + link = link.replace(/^www\./i, 'http://www.');
  1266 + }
  1267 + return '<a href="' + link + '">' + lnkTxt + '</a>';
  1268 + }
  1269 +
  1270 + function replaceMail(wholeMatch, m1) {
  1271 + var unescapedStr = showdown.subParser('unescapeSpecialChars')(m1);
  1272 + return showdown.subParser('encodeEmailAddress')(unescapedStr);
  1273 + }
  1274 +
  1275 + text = globals.converter._dispatch('autoLinks.after', text, options, globals);
  1276 +
  1277 + return text;
  1278 +});
  1279 +
  1280 +/**
  1281 + * These are all the transformations that form block-level
  1282 + * tags like paragraphs, headers, and list items.
  1283 + */
  1284 +showdown.subParser('blockGamut', function (text, options, globals) {
  1285 + 'use strict';
  1286 +
  1287 + text = globals.converter._dispatch('blockGamut.before', text, options, globals);
  1288 +
  1289 + // we parse blockquotes first so that we can have headings and hrs
  1290 + // inside blockquotes
  1291 + text = showdown.subParser('blockQuotes')(text, options, globals);
  1292 + text = showdown.subParser('headers')(text, options, globals);
  1293 +
  1294 + // Do Horizontal Rules:
  1295 + var key = showdown.subParser('hashBlock')('<hr />', options, globals);
  1296 + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
  1297 + text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
  1298 + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key);
  1299 +
  1300 + text = showdown.subParser('lists')(text, options, globals);
  1301 + text = showdown.subParser('codeBlocks')(text, options, globals);
  1302 + text = showdown.subParser('tables')(text, options, globals);
  1303 +
  1304 + // We already ran _HashHTMLBlocks() before, in Markdown(), but that
  1305 + // was to escape raw HTML in the original Markdown source. This time,
  1306 + // we're escaping the markup we've just created, so that we don't wrap
  1307 + // <p> tags around block-level tags.
  1308 + text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
  1309 + text = showdown.subParser('paragraphs')(text, options, globals);
  1310 +
  1311 + text = globals.converter._dispatch('blockGamut.after', text, options, globals);
  1312 +
  1313 + return text;
  1314 +});
  1315 +
  1316 +showdown.subParser('blockQuotes', function (text, options, globals) {
  1317 + 'use strict';
  1318 +
  1319 + text = globals.converter._dispatch('blockQuotes.before', text, options, globals);
  1320 + /*
  1321 + text = text.replace(/
  1322 + ( // Wrap whole match in $1
  1323 + (
  1324 + ^[ \t]*>[ \t]? // '>' at the start of a line
  1325 + .+\n // rest of the first line
  1326 + (.+\n)* // subsequent consecutive lines
  1327 + \n* // blanks
  1328 + )+
  1329 + )
  1330 + /gm, function(){...});
  1331 + */
  1332 +
  1333 + text = text.replace(/((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm, function (wholeMatch, m1) {
  1334 + var bq = m1;
  1335 +
  1336 + // attacklab: hack around Konqueror 3.5.4 bug:
  1337 + // "----------bug".replace(/^-/g,"") == "bug"
  1338 + bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting
  1339 +
  1340 + // attacklab: clean up hack
  1341 + bq = bq.replace(/~0/g, '');
  1342 +
  1343 + bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines
  1344 + bq = showdown.subParser('githubCodeBlocks')(bq, options, globals);
  1345 + bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse
  1346 +
  1347 + bq = bq.replace(/(^|\n)/g, '$1 ');
  1348 + // These leading spaces screw with <pre> content, so we need to fix that:
  1349 + bq = bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
  1350 + var pre = m1;
  1351 + // attacklab: hack around Konqueror 3.5.4 bug:
  1352 + pre = pre.replace(/^ /mg, '~0');
  1353 + pre = pre.replace(/~0/g, '');
  1354 + return pre;
  1355 + });
  1356 +
  1357 + return showdown.subParser('hashBlock')('<blockquote>\n' + bq + '\n</blockquote>', options, globals);
  1358 + });
  1359 +
  1360 + text = globals.converter._dispatch('blockQuotes.after', text, options, globals);
  1361 + return text;
  1362 +});
  1363 +
  1364 +/**
  1365 + * Process Markdown `<pre><code>` blocks.
  1366 + */
  1367 +showdown.subParser('codeBlocks', function (text, options, globals) {
  1368 + 'use strict';
  1369 +
  1370 + text = globals.converter._dispatch('codeBlocks.before', text, options, globals);
  1371 + /*
  1372 + text = text.replace(text,
  1373 + /(?:\n\n|^)
  1374 + ( // $1 = the code block -- one or more lines, starting with a space/tab
  1375 + (?:
  1376 + (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
  1377 + .*\n+
  1378 + )+
  1379 + )
  1380 + (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
  1381 + /g,function(){...});
  1382 + */
  1383 +
  1384 + // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  1385 + text += '~0';
  1386 +
  1387 + var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
  1388 + text = text.replace(pattern, function (wholeMatch, m1, m2) {
  1389 + var codeblock = m1,
  1390 + nextChar = m2,
  1391 + end = '\n';
  1392 +
  1393 + codeblock = showdown.subParser('outdent')(codeblock);
  1394 + codeblock = showdown.subParser('encodeCode')(codeblock);
  1395 + codeblock = showdown.subParser('detab')(codeblock);
  1396 + codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
  1397 + codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines
  1398 +
  1399 + if (options.omitExtraWLInCodeBlocks) {
  1400 + end = '';
  1401 + }
  1402 +
  1403 + codeblock = '<pre><code>' + codeblock + end + '</code></pre>';
  1404 +
  1405 + return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;
  1406 + });
  1407 +
  1408 + // attacklab: strip sentinel
  1409 + text = text.replace(/~0/, '');
  1410 +
  1411 + text = globals.converter._dispatch('codeBlocks.after', text, options, globals);
  1412 + return text;
  1413 +});
  1414 +
  1415 +/**
  1416 + *
  1417 + * * Backtick quotes are used for <code></code> spans.
  1418 + *
  1419 + * * You can use multiple backticks as the delimiters if you want to
  1420 + * include literal backticks in the code span. So, this input:
  1421 + *
  1422 + * Just type ``foo `bar` baz`` at the prompt.
  1423 + *
  1424 + * Will translate to:
  1425 + *
  1426 + * <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
  1427 + *
  1428 + * There's no arbitrary limit to the number of backticks you
  1429 + * can use as delimters. If you need three consecutive backticks
  1430 + * in your code, use four for delimiters, etc.
  1431 + *
  1432 + * * You can use spaces to get literal backticks at the edges:
  1433 + *
  1434 + * ... type `` `bar` `` ...
  1435 + *
  1436 + * Turns to:
  1437 + *
  1438 + * ... type <code>`bar`</code> ...
  1439 + */
  1440 +showdown.subParser('codeSpans', function (text, options, globals) {
  1441 + 'use strict';
  1442 +
  1443 + text = globals.converter._dispatch('codeSpans.before', text, options, globals);
  1444 +
  1445 + /*
  1446 + text = text.replace(/
  1447 + (^|[^\\]) // Character before opening ` can't be a backslash
  1448 + (`+) // $2 = Opening run of `
  1449 + ( // $3 = The code block
  1450 + [^\r]*?
  1451 + [^`] // attacklab: work around lack of lookbehind
  1452 + )
  1453 + \2 // Matching closer
  1454 + (?!`)
  1455 + /gm, function(){...});
  1456 + */
  1457 +
  1458 + if (typeof(text) === 'undefined') {
  1459 + text = '';
  1460 + }
  1461 + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
  1462 + function (wholeMatch, m1, m2, m3) {
  1463 + var c = m3;
  1464 + c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
  1465 + c = c.replace(/[ \t]*$/g, ''); // trailing whitespace
  1466 + c = showdown.subParser('encodeCode')(c);
  1467 + return m1 + '<code>' + c + '</code>';
  1468 + }
  1469 + );
  1470 +
  1471 + text = globals.converter._dispatch('codeSpans.after', text, options, globals);
  1472 + return text;
  1473 +});
  1474 +
  1475 +/**
  1476 + * Convert all tabs to spaces
  1477 + */
  1478 +showdown.subParser('detab', function (text) {
  1479 + 'use strict';
  1480 +
  1481 + // expand first n-1 tabs
  1482 + text = text.replace(/\t(?=\t)/g, ' '); // g_tab_width
  1483 +
  1484 + // replace the nth with two sentinels
  1485 + text = text.replace(/\t/g, '~A~B');
  1486 +
  1487 + // use the sentinel to anchor our regex so it doesn't explode
  1488 + text = text.replace(/~B(.+?)~A/g, function (wholeMatch, m1) {
  1489 + var leadingText = m1,
  1490 + numSpaces = 4 - leadingText.length % 4; // g_tab_width
  1491 +
  1492 + // there *must* be a better way to do this:
  1493 + for (var i = 0; i < numSpaces; i++) {
  1494 + leadingText += ' ';
  1495 + }
  1496 +
  1497 + return leadingText;
  1498 + });
  1499 +
  1500 + // clean up sentinels
  1501 + text = text.replace(/~A/g, ' '); // g_tab_width
  1502 + text = text.replace(/~B/g, '');
  1503 +
  1504 + return text;
  1505 +
  1506 +});
  1507 +
  1508 +/**
  1509 + * Smart processing for ampersands and angle brackets that need to be encoded.
  1510 + */
  1511 +showdown.subParser('encodeAmpsAndAngles', function (text) {
  1512 + 'use strict';
  1513 + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
  1514 + // http://bumppo.net/projects/amputator/
  1515 + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&amp;');
  1516 +
  1517 + // Encode naked <'s
  1518 + text = text.replace(/<(?![a-z\/?\$!])/gi, '&lt;');
  1519 +
  1520 + return text;
  1521 +});
  1522 +
  1523 +/**
  1524 + * Returns the string, with after processing the following backslash escape sequences.
  1525 + *
  1526 + * attacklab: The polite way to do this is with the new escapeCharacters() function:
  1527 + *
  1528 + * text = escapeCharacters(text,"\\",true);
  1529 + * text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
  1530 + *
  1531 + * ...but we're sidestepping its use of the (slow) RegExp constructor
  1532 + * as an optimization for Firefox. This function gets called a LOT.
  1533 + */
  1534 +showdown.subParser('encodeBackslashEscapes', function (text) {
  1535 + 'use strict';
  1536 + text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
  1537 + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback);
  1538 + return text;
  1539 +});
  1540 +
  1541 +/**
  1542 + * Encode/escape certain characters inside Markdown code runs.
  1543 + * The point is that in code, these characters are literals,
  1544 + * and lose their special Markdown meanings.
  1545 + */
  1546 +showdown.subParser('encodeCode', function (text) {
  1547 + 'use strict';
  1548 +
  1549 + // Encode all ampersands; HTML entities are not
  1550 + // entities within a Markdown code span.
  1551 + text = text.replace(/&/g, '&amp;');
  1552 +
  1553 + // Do the angle bracket song and dance:
  1554 + text = text.replace(/</g, '&lt;');
  1555 + text = text.replace(/>/g, '&gt;');
  1556 +
  1557 + // Now, escape characters that are magic in Markdown:
  1558 + text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false);
  1559 +
  1560 + // jj the line above breaks this:
  1561 + //---
  1562 + //* Item
  1563 + // 1. Subitem
  1564 + // special char: *
  1565 + // ---
  1566 +
  1567 + return text;
  1568 +});
  1569 +
  1570 +/**
  1571 + * Input: an email address, e.g. "foo@example.com"
  1572 + *
  1573 + * Output: the email address as a mailto link, with each character
  1574 + * of the address encoded as either a decimal or hex entity, in
  1575 + * the hopes of foiling most address harvesting spam bots. E.g.:
  1576 + *
  1577 + * <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
  1578 + * x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
  1579 + * &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
  1580 + *
  1581 + * Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
  1582 + * mailing list: <http://tinyurl.com/yu7ue>
  1583 + *
  1584 + */
  1585 +showdown.subParser('encodeEmailAddress', function (addr) {
  1586 + 'use strict';
  1587 +
  1588 + var encode = [
  1589 + function (ch) {
  1590 + return '&#' + ch.charCodeAt(0) + ';';
  1591 + },
  1592 + function (ch) {
  1593 + return '&#x' + ch.charCodeAt(0).toString(16) + ';';
  1594 + },
  1595 + function (ch) {
  1596 + return ch;
  1597 + }
  1598 + ];
  1599 +
  1600 + addr = 'mailto:' + addr;
  1601 +
  1602 + addr = addr.replace(/./g, function (ch) {
  1603 + if (ch === '@') {
  1604 + // this *must* be encoded. I insist.
  1605 + ch = encode[Math.floor(Math.random() * 2)](ch);
  1606 + } else if (ch !== ':') {
  1607 + // leave ':' alone (to spot mailto: later)
  1608 + var r = Math.random();
  1609 + // roughly 10% raw, 45% hex, 45% dec
  1610 + ch = (
  1611 + r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
  1612 + );
  1613 + }
  1614 + return ch;
  1615 + });
  1616 +
  1617 + addr = '<a href="' + addr + '">' + addr + '</a>';
  1618 + addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part
  1619 +
  1620 + return addr;
  1621 +});
  1622 +
  1623 +/**
  1624 + * Within tags -- meaning between < and > -- encode [\ ` * _] so they
  1625 + * don't conflict with their use in Markdown for code, italics and strong.
  1626 + */
  1627 +showdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text) {
  1628 + 'use strict';
  1629 +
  1630 + // Build a regex to find HTML tags and comments. See Friedl's
  1631 + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
  1632 + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
  1633 +
  1634 + text = text.replace(regex, function (wholeMatch) {
  1635 + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`');
  1636 + tag = showdown.helper.escapeCharacters(tag, '\\`*_', false);
  1637 + return tag;
  1638 + });
  1639 +
  1640 + return text;
  1641 +});
  1642 +
  1643 +/**
  1644 + * Handle github codeblocks prior to running HashHTML so that
  1645 + * HTML contained within the codeblock gets escaped properly
  1646 + * Example:
  1647 + * ```ruby
  1648 + * def hello_world(x)
  1649 + * puts "Hello, #{x}"
  1650 + * end
  1651 + * ```
  1652 + */
  1653 +showdown.subParser('githubCodeBlocks', function (text, options, globals) {
  1654 + 'use strict';
  1655 +
  1656 + // early exit if option is not enabled
  1657 + if (!options.ghCodeBlocks) {
  1658 + return text;
  1659 + }
  1660 +
  1661 + text = globals.converter._dispatch('githubCodeBlocks.before', text, options, globals);
  1662 +
  1663 + text += '~0';
  1664 +
  1665 + text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) {
  1666 + var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n';
  1667 +
  1668 + // First parse the github code block
  1669 + codeblock = showdown.subParser('encodeCode')(codeblock);
  1670 + codeblock = showdown.subParser('detab')(codeblock);
  1671 + codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
  1672 + codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace
  1673 +
  1674 + codeblock = '<pre><code' + (language ? ' class="' + language + ' language-' + language + '"' : '') + '>' + codeblock + end + '</code></pre>';
  1675 +
  1676 + codeblock = showdown.subParser('hashBlock')(codeblock, options, globals);
  1677 +
  1678 + // Since GHCodeblocks can be false positives, we need to
  1679 + // store the primitive text and the parsed text in a global var,
  1680 + // and then return a token
  1681 + return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
  1682 + });
  1683 +
  1684 + // attacklab: strip sentinel
  1685 + text = text.replace(/~0/, '');
  1686 +
  1687 + return globals.converter._dispatch('githubCodeBlocks.after', text, options, globals);
  1688 +});
  1689 +
  1690 +showdown.subParser('hashBlock', function (text, options, globals) {
  1691 + 'use strict';
  1692 + text = text.replace(/(^\n+|\n+$)/g, '');
  1693 + return '\n\n~K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n';
  1694 +});
  1695 +
  1696 +showdown.subParser('hashElement', function (text, options, globals) {
  1697 + 'use strict';
  1698 +
  1699 + return function (wholeMatch, m1) {
  1700 + var blockText = m1;
  1701 +
  1702 + // Undo double lines
  1703 + blockText = blockText.replace(/\n\n/g, '\n');
  1704 + blockText = blockText.replace(/^\n/, '');
  1705 +
  1706 + // strip trailing blank lines
  1707 + blockText = blockText.replace(/\n+$/g, '');
  1708 +
  1709 + // Replace the element text with a marker ("~KxK" where x is its key)
  1710 + blockText = '\n\n~K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n';
  1711 +
  1712 + return blockText;
  1713 + };
  1714 +});
  1715 +
  1716 +showdown.subParser('hashHTMLBlocks', function (text, options, globals) {
  1717 + 'use strict';
  1718 +
  1719 + var blockTags = [
  1720 + 'pre',
  1721 + 'div',
  1722 + 'h1',
  1723 + 'h2',
  1724 + 'h3',
  1725 + 'h4',
  1726 + 'h5',
  1727 + 'h6',
  1728 + 'blockquote',
  1729 + 'table',
  1730 + 'dl',
  1731 + 'ol',
  1732 + 'ul',
  1733 + 'script',
  1734 + 'noscript',
  1735 + 'form',
  1736 + 'fieldset',
  1737 + 'iframe',
  1738 + 'math',
  1739 + 'style',
  1740 + 'section',
  1741 + 'header',
  1742 + 'footer',
  1743 + 'nav',
  1744 + 'article',
  1745 + 'aside',
  1746 + 'address',
  1747 + 'audio',
  1748 + 'canvas',
  1749 + 'figure',
  1750 + 'hgroup',
  1751 + 'output',
  1752 + 'video',
  1753 + 'p'
  1754 + ],
  1755 + repFunc = function (wholeMatch, match, left, right) {
  1756 + var txt = wholeMatch;
  1757 + // check if this html element is marked as markdown
  1758 + // if so, it's contents should be parsed as markdown
  1759 + if (left.search(/\bmarkdown\b/) !== -1) {
  1760 + txt = left + globals.converter.makeHtml(match) + right;
  1761 + }
  1762 + return '\n\n~K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
  1763 + };
  1764 +
  1765 + for (var i = 0; i < blockTags.length; ++i) {
  1766 + text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<' + blockTags[i] + '\\b[^>]*>', '</' + blockTags[i] + '>', 'gim');
  1767 + }
  1768 +
  1769 + // HR SPECIAL CASE
  1770 + text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
  1771 + showdown.subParser('hashElement')(text, options, globals));
  1772 +
  1773 + // Special case for standalone HTML comments:
  1774 + text = text.replace(/(<!--[\s\S]*?-->)/g,
  1775 + showdown.subParser('hashElement')(text, options, globals));
  1776 +
  1777 + // PHP and ASP-style processor instructions (<?...?> and <%...%>)
  1778 + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,
  1779 + showdown.subParser('hashElement')(text, options, globals));
  1780 + return text;
  1781 +});
  1782 +
  1783 +/**
  1784 + * Hash span elements that should not be parsed as markdown
  1785 + */
  1786 +showdown.subParser('hashHTMLSpans', function (text, config, globals) {
  1787 + 'use strict';
  1788 +
  1789 + var matches = showdown.helper.matchRecursiveRegExp(text, '<code\\b[^>]*>', '</code>', 'gi');
  1790 +
  1791 + for (var i = 0; i < matches.length; ++i) {
  1792 + text = text.replace(matches[i][0], '~L' + (globals.gHtmlSpans.push(matches[i][0]) - 1) + 'L');
  1793 + }
  1794 + return text;
  1795 +});
  1796 +
  1797 +/**
  1798 + * Unhash HTML spans
  1799 + */
  1800 +showdown.subParser('unhashHTMLSpans', function (text, config, globals) {
  1801 + 'use strict';
  1802 +
  1803 + for (var i = 0; i < globals.gHtmlSpans.length; ++i) {
  1804 + text = text.replace('~L' + i + 'L', globals.gHtmlSpans[i]);
  1805 + }
  1806 +
  1807 + return text;
  1808 +});
  1809 +
  1810 +/**
  1811 + * Hash span elements that should not be parsed as markdown
  1812 + */
  1813 +showdown.subParser('hashPreCodeTags', function (text, config, globals) {
  1814 + 'use strict';
  1815 +
  1816 + var repFunc = function (wholeMatch, match, left, right) {
  1817 + // encode html entities
  1818 + var codeblock = left + showdown.subParser('encodeCode')(match) + right;
  1819 + return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
  1820 + };
  1821 +
  1822 + text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>', '^(?: |\\t){0,3}</code>\\s*</pre>', 'gim');
  1823 + return text;
  1824 +});
  1825 +
  1826 +showdown.subParser('headers', function (text, options, globals) {
  1827 + 'use strict';
  1828 +
  1829 + text = globals.converter._dispatch('headers.before', text, options, globals);
  1830 +
  1831 + var prefixHeader = options.prefixHeaderId,
  1832 + headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),
  1833 +
  1834 + // Set text-style headers:
  1835 + // Header 1
  1836 + // ========
  1837 + //
  1838 + // Header 2
  1839 + // --------
  1840 + //
  1841 + setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm,
  1842 + setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm;
  1843 +
  1844 + text = text.replace(setextRegexH1, function (wholeMatch, m1) {
  1845 +
  1846 + var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
  1847 + hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
  1848 + hLevel = headerLevelStart,
  1849 + hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>';
  1850 + return showdown.subParser('hashBlock')(hashBlock, options, globals);
  1851 + });
  1852 +
  1853 + text = text.replace(setextRegexH2, function (matchFound, m1) {
  1854 + var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
  1855 + hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
  1856 + hLevel = headerLevelStart + 1,
  1857 + hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>';
  1858 + return showdown.subParser('hashBlock')(hashBlock, options, globals);
  1859 + });
  1860 +
  1861 + // atx-style headers:
  1862 + // # Header 1
  1863 + // ## Header 2
  1864 + // ## Header 2 with closing hashes ##
  1865 + // ...
  1866 + // ###### Header 6
  1867 + //
  1868 + text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) {
  1869 + var span = showdown.subParser('spanGamut')(m2, options, globals),
  1870 + hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"',
  1871 + hLevel = headerLevelStart - 1 + m1.length,
  1872 + header = '<h' + hLevel + hID + '>' + span + '</h' + hLevel + '>';
  1873 +
  1874 + return showdown.subParser('hashBlock')(header, options, globals);
  1875 + });
  1876 +
  1877 + function headerId(m) {
  1878 + var title, escapedId = m.replace(/[^\w]/g, '').toLowerCase();
  1879 +
  1880 + if (globals.hashLinkCounts[escapedId]) {
  1881 + title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++);
  1882 + } else {
  1883 + title = escapedId;
  1884 + globals.hashLinkCounts[escapedId] = 1;
  1885 + }
  1886 +
  1887 + // Prefix id to prevent causing inadvertent pre-existing style matches.
  1888 + if (prefixHeader === true) {
  1889 + prefixHeader = 'section';
  1890 + }
  1891 +
  1892 + if (showdown.helper.isString(prefixHeader)) {
  1893 + return prefixHeader + title;
  1894 + }
  1895 + return title;
  1896 + }
  1897 +
  1898 + text = globals.converter._dispatch('headers.after', text, options, globals);
  1899 + return text;
  1900 +});
  1901 +
  1902 +/**
  1903 + * Turn Markdown image shortcuts into <img> tags.
  1904 + */
  1905 +showdown.subParser('images', function (text, options, globals) {
  1906 + 'use strict';
  1907 +
  1908 + text = globals.converter._dispatch('images.before', text, options, globals);
  1909 +
  1910 + var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,
  1911 + referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;
  1912 +
  1913 + function writeImageTag (wholeMatch, altText, linkId, url, width, height, m5, title) {
  1914 +
  1915 + var gUrls = globals.gUrls,
  1916 + gTitles = globals.gTitles,
  1917 + gDims = globals.gDimensions;
  1918 +
  1919 + linkId = linkId.toLowerCase();
  1920 +
  1921 + if (!title) {
  1922 + title = '';
  1923 + }
  1924 +
  1925 + if (url === '' || url === null) {
  1926 + if (linkId === '' || linkId === null) {
  1927 + // lower-case and turn embedded newlines into spaces
  1928 + linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
  1929 + }
  1930 + url = '#' + linkId;
  1931 +
  1932 + if (!showdown.helper.isUndefined(gUrls[linkId])) {
  1933 + url = gUrls[linkId];
  1934 + if (!showdown.helper.isUndefined(gTitles[linkId])) {
  1935 + title = gTitles[linkId];
  1936 + }
  1937 + if (!showdown.helper.isUndefined(gDims[linkId])) {
  1938 + width = gDims[linkId].width;
  1939 + height = gDims[linkId].height;
  1940 + }
  1941 + } else {
  1942 + return wholeMatch;
  1943 + }
  1944 + }
  1945 +
  1946 + altText = altText.replace(/"/g, '&quot;');
  1947 + altText = showdown.helper.escapeCharacters(altText, '*_', false);
  1948 + url = showdown.helper.escapeCharacters(url, '*_', false);
  1949 + var result = '<img src="' + url + '" alt="' + altText + '"';
  1950 +
  1951 + if (title) {
  1952 + title = title.replace(/"/g, '&quot;');
  1953 + title = showdown.helper.escapeCharacters(title, '*_', false);
  1954 + result += ' title="' + title + '"';
  1955 + }
  1956 +
  1957 + if (width && height) {
  1958 + width = (width === '*') ? 'auto' : width;
  1959 + height = (height === '*') ? 'auto' : height;
  1960 +
  1961 + result += ' width="' + width + '"';
  1962 + result += ' height="' + height + '"';
  1963 + }
  1964 +
  1965 + result += ' />';
  1966 + return result;
  1967 + }
  1968 +
  1969 + // First, handle reference-style labeled images: ![alt text][id]
  1970 + text = text.replace(referenceRegExp, writeImageTag);
  1971 +
  1972 + // Next, handle inline images: ![alt text](url =<width>x<height> "optional title")
  1973 + text = text.replace(inlineRegExp, writeImageTag);
  1974 +
  1975 + text = globals.converter._dispatch('images.after', text, options, globals);
  1976 + return text;
  1977 +});
  1978 +
  1979 +showdown.subParser('italicsAndBold', function (text, options, globals) {
  1980 + 'use strict';
  1981 +
  1982 + text = globals.converter._dispatch('italicsAndBold.before', text, options, globals);
  1983 +
  1984 + if (options.literalMidWordUnderscores) {
  1985 + //underscores
  1986 + // Since we are consuming a \s character, we need to add it
  1987 + text = text.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm, '$1<strong>$2</strong>');
  1988 + text = text.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm, '$1<em>$2</em>');
  1989 + //asterisks
  1990 + text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g, '<strong>$2</strong>');
  1991 + text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
  1992 +
  1993 + } else {
  1994 + // <strong> must go first:
  1995 + text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>');
  1996 + text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
  1997 + }
  1998 +
  1999 + text = globals.converter._dispatch('italicsAndBold.after', text, options, globals);
  2000 + return text;
  2001 +});
  2002 +
  2003 +/**
  2004 + * Form HTML ordered (numbered) and unordered (bulleted) lists.
  2005 + */
  2006 +showdown.subParser('lists', function (text, options, globals) {
  2007 + 'use strict';
  2008 +
  2009 + text = globals.converter._dispatch('lists.before', text, options, globals);
  2010 + /**
  2011 + * Process the contents of a single ordered or unordered list, splitting it
  2012 + * into individual list items.
  2013 + * @param {string} listStr
  2014 + * @param {boolean} trimTrailing
  2015 + * @returns {string}
  2016 + */
  2017 + function processListItems (listStr, trimTrailing) {
  2018 + // The $g_list_level global keeps track of when we're inside a list.
  2019 + // Each time we enter a list, we increment it; when we leave a list,
  2020 + // we decrement. If it's zero, we're not in a list anymore.
  2021 + //
  2022 + // We do this because when we're not inside a list, we want to treat
  2023 + // something like this:
  2024 + //
  2025 + // I recommend upgrading to version
  2026 + // 8. Oops, now this line is treated
  2027 + // as a sub-list.
  2028 + //
  2029 + // As a single paragraph, despite the fact that the second line starts
  2030 + // with a digit-period-space sequence.
  2031 + //
  2032 + // Whereas when we're inside a list (or sub-list), that line will be
  2033 + // treated as the start of a sub-list. What a kludge, huh? This is
  2034 + // an aspect of Markdown's syntax that's hard to parse perfectly
  2035 + // without resorting to mind-reading. Perhaps the solution is to
  2036 + // change the syntax rules such that sub-lists must start with a
  2037 + // starting cardinal number; e.g. "1." or "a.".
  2038 + globals.gListLevel++;
  2039 +
  2040 + // trim trailing blank lines:
  2041 + listStr = listStr.replace(/\n{2,}$/, '\n');
  2042 +
  2043 + // attacklab: add sentinel to emulate \z
  2044 + listStr += '~0';
  2045 +
  2046 + var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
  2047 + isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr));
  2048 +
  2049 + listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
  2050 + checked = (checked && checked.trim() !== '');
  2051 + var item = showdown.subParser('outdent')(m4, options, globals),
  2052 + bulletStyle = '';
  2053 +
  2054 + // Support for github tasklists
  2055 + if (taskbtn && options.tasklists) {
  2056 + bulletStyle = ' class="task-list-item" style="list-style-type: none;"';
  2057 + item = item.replace(/^[ \t]*\[(x|X| )?]/m, function () {
  2058 + var otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';
  2059 + if (checked) {
  2060 + otp += ' checked';
  2061 + }
  2062 + otp += '>';
  2063 + return otp;
  2064 + });
  2065 + }
  2066 + // m1 - Leading line or
  2067 + // Has a double return (multi paragraph) or
  2068 + // Has sublist
  2069 + if (m1 || (item.search(/\n{2,}/) > -1)) {
  2070 + item = showdown.subParser('githubCodeBlocks')(item, options, globals);
  2071 + item = showdown.subParser('blockGamut')(item, options, globals);
  2072 + } else {
  2073 + // Recursion for sub-lists:
  2074 + item = showdown.subParser('lists')(item, options, globals);
  2075 + item = item.replace(/\n$/, ''); // chomp(item)
  2076 + if (isParagraphed) {
  2077 + item = showdown.subParser('paragraphs')(item, options, globals);
  2078 + } else {
  2079 + item = showdown.subParser('spanGamut')(item, options, globals);
  2080 + }
  2081 + }
  2082 + item = '\n<li' + bulletStyle + '>' + item + '</li>\n';
  2083 + return item;
  2084 + });
  2085 +
  2086 + // attacklab: strip sentinel
  2087 + listStr = listStr.replace(/~0/g, '');
  2088 +
  2089 + globals.gListLevel--;
  2090 +
  2091 + if (trimTrailing) {
  2092 + listStr = listStr.replace(/\s+$/, '');
  2093 + }
  2094 +
  2095 + return listStr;
  2096 + }
  2097 +
  2098 + /**
  2099 + * Check and parse consecutive lists (better fix for issue #142)
  2100 + * @param {string} list
  2101 + * @param {string} listType
  2102 + * @param {boolean} trimTrailing
  2103 + * @returns {string}
  2104 + */
  2105 + function parseConsecutiveLists(list, listType, trimTrailing) {
  2106 + // check if we caught 2 or more consecutive lists by mistake
  2107 + // we use the counterRgx, meaning if listType is UL we look for UL and vice versa
  2108 + var counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm,
  2109 + subLists = [],
  2110 + result = '';
  2111 +
  2112 + if (list.search(counterRxg) !== -1) {
  2113 + (function parseCL(txt) {
  2114 + var pos = txt.search(counterRxg);
  2115 + if (pos !== -1) {
  2116 + // slice
  2117 + result += '\n\n<' + listType + '>' + processListItems(txt.slice(0, pos), !!trimTrailing) + '</' + listType + '>\n\n';
  2118 +
  2119 + // invert counterType and listType
  2120 + listType = (listType === 'ul') ? 'ol' : 'ul';
  2121 + counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;
  2122 +
  2123 + //recurse
  2124 + parseCL(txt.slice(pos));
  2125 + } else {
  2126 + result += '\n\n<' + listType + '>' + processListItems(txt, !!trimTrailing) + '</' + listType + '>\n\n';
  2127 + }
  2128 + })(list);
  2129 + for (var i = 0; i < subLists.length; ++i) {
  2130 +
  2131 + }
  2132 + } else {
  2133 + result = '\n\n<' + listType + '>' + processListItems(list, !!trimTrailing) + '</' + listType + '>\n\n';
  2134 + }
  2135 +
  2136 + return result;
  2137 + }
  2138 +
  2139 + // attacklab: add sentinel to hack around khtml/safari bug:
  2140 + // http://bugs.webkit.org/show_bug.cgi?id=11231
  2141 + text += '~0';
  2142 +
  2143 + // Re-usable pattern to match any entire ul or ol list:
  2144 + var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  2145 +
  2146 + if (globals.gListLevel) {
  2147 + text = text.replace(wholeList, function (wholeMatch, list, m2) {
  2148 + var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
  2149 + return parseConsecutiveLists(list, listType, true);
  2150 + });
  2151 + } else {
  2152 + wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  2153 + //wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g;
  2154 + text = text.replace(wholeList, function (wholeMatch, m1, list, m3) {
  2155 +
  2156 + var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
  2157 + return parseConsecutiveLists(list, listType);
  2158 + });
  2159 + }
  2160 +
  2161 + // attacklab: strip sentinel
  2162 + text = text.replace(/~0/, '');
  2163 +
  2164 + text = globals.converter._dispatch('lists.after', text, options, globals);
  2165 + return text;
  2166 +});
  2167 +
  2168 +/**
  2169 + * Remove one level of line-leading tabs or spaces
  2170 + */
  2171 +showdown.subParser('outdent', function (text) {
  2172 + 'use strict';
  2173 +
  2174 + // attacklab: hack around Konqueror 3.5.4 bug:
  2175 + // "----------bug".replace(/^-/g,"") == "bug"
  2176 + text = text.replace(/^(\t|[ ]{1,4})/gm, '~0'); // attacklab: g_tab_width
  2177 +
  2178 + // attacklab: clean up hack
  2179 + text = text.replace(/~0/g, '');
  2180 +
  2181 + return text;
  2182 +});
  2183 +
  2184 +/**
  2185 + *
  2186 + */
  2187 +showdown.subParser('paragraphs', function (text, options, globals) {
  2188 + 'use strict';
  2189 +
  2190 + text = globals.converter._dispatch('paragraphs.before', text, options, globals);
  2191 + // Strip leading and trailing lines:
  2192 + text = text.replace(/^\n+/g, '');
  2193 + text = text.replace(/\n+$/g, '');
  2194 +
  2195 + var grafs = text.split(/\n{2,}/g),
  2196 + grafsOut = [],
  2197 + end = grafs.length; // Wrap <p> tags
  2198 +
  2199 + for (var i = 0; i < end; i++) {
  2200 + var str = grafs[i];
  2201 + // if this is an HTML marker, copy it
  2202 + if (str.search(/~(K|G)(\d+)\1/g) >= 0) {
  2203 + grafsOut.push(str);
  2204 + } else {
  2205 + str = showdown.subParser('spanGamut')(str, options, globals);
  2206 + str = str.replace(/^([ \t]*)/g, '<p>');
  2207 + str += '</p>';
  2208 + grafsOut.push(str);
  2209 + }
  2210 + }
  2211 +
  2212 + /** Unhashify HTML blocks */
  2213 + end = grafsOut.length;
  2214 + for (i = 0; i < end; i++) {
  2215 + var blockText = '',
  2216 + grafsOutIt = grafsOut[i],
  2217 + codeFlag = false;
  2218 + // if this is a marker for an html block...
  2219 + while (grafsOutIt.search(/~(K|G)(\d+)\1/) >= 0) {
  2220 + var delim = RegExp.$1,
  2221 + num = RegExp.$2;
  2222 +
  2223 + if (delim === 'K') {
  2224 + blockText = globals.gHtmlBlocks[num];
  2225 + } else {
  2226 + // we need to check if ghBlock is a false positive
  2227 + if (codeFlag) {
  2228 + // use encoded version of all text
  2229 + blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text);
  2230 + } else {
  2231 + blockText = globals.ghCodeBlocks[num].codeblock;
  2232 + }
  2233 + }
  2234 + blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs
  2235 +
  2236 + grafsOutIt = grafsOutIt.replace(/(\n\n)?~(K|G)\d+\2(\n\n)?/, blockText);
  2237 + // Check if grafsOutIt is a pre->code
  2238 + if (/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(grafsOutIt)) {
  2239 + codeFlag = true;
  2240 + }
  2241 + }
  2242 + grafsOut[i] = grafsOutIt;
  2243 + }
  2244 + text = grafsOut.join('\n\n');
  2245 + // Strip leading and trailing lines:
  2246 + text = text.replace(/^\n+/g, '');
  2247 + text = text.replace(/\n+$/g, '');
  2248 + return globals.converter._dispatch('paragraphs.after', text, options, globals);
  2249 +});
  2250 +
  2251 +/**
  2252 + * Run extension
  2253 + */
  2254 +showdown.subParser('runExtension', function (ext, text, options, globals) {
  2255 + 'use strict';
  2256 +
  2257 + if (ext.filter) {
  2258 + text = ext.filter(text, globals.converter, options);
  2259 +
  2260 + } else if (ext.regex) {
  2261 + // TODO remove this when old extension loading mechanism is deprecated
  2262 + var re = ext.regex;
  2263 + if (!re instanceof RegExp) {
  2264 + re = new RegExp(re, 'g');
  2265 + }
  2266 + text = text.replace(re, ext.replace);
  2267 + }
  2268 +
  2269 + return text;
  2270 +});
  2271 +
  2272 +/**
  2273 + * These are all the transformations that occur *within* block-level
  2274 + * tags like paragraphs, headers, and list items.
  2275 + */
  2276 +showdown.subParser('spanGamut', function (text, options, globals) {
  2277 + 'use strict';
  2278 +
  2279 + text = globals.converter._dispatch('spanGamut.before', text, options, globals);
  2280 + text = showdown.subParser('codeSpans')(text, options, globals);
  2281 + text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);
  2282 + text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);
  2283 +
  2284 + // Process anchor and image tags. Images must come first,
  2285 + // because ![foo][f] looks like an anchor.
  2286 + text = showdown.subParser('images')(text, options, globals);
  2287 + text = showdown.subParser('anchors')(text, options, globals);
  2288 +
  2289 + // Make links out of things like `<http://example.com/>`
  2290 + // Must come after _DoAnchors(), because you can use < and >
  2291 + // delimiters in inline links like [this](<url>).
  2292 + text = showdown.subParser('autoLinks')(text, options, globals);
  2293 + text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals);
  2294 + text = showdown.subParser('italicsAndBold')(text, options, globals);
  2295 + text = showdown.subParser('strikethrough')(text, options, globals);
  2296 +
  2297 + // Do hard breaks:
  2298 + text = text.replace(/ +\n/g, ' <br />\n');
  2299 +
  2300 + text = globals.converter._dispatch('spanGamut.after', text, options, globals);
  2301 + return text;
  2302 +});
  2303 +
  2304 +showdown.subParser('strikethrough', function (text, options, globals) {
  2305 + 'use strict';
  2306 +
  2307 + if (options.strikethrough) {
  2308 + text = globals.converter._dispatch('strikethrough.before', text, options, globals);
  2309 + text = text.replace(/(?:~T){2}([\s\S]+?)(?:~T){2}/g, '<del>$1</del>');
  2310 + text = globals.converter._dispatch('strikethrough.after', text, options, globals);
  2311 + }
  2312 +
  2313 + return text;
  2314 +});
  2315 +
  2316 +/**
  2317 + * Strip any lines consisting only of spaces and tabs.
  2318 + * This makes subsequent regexs easier to write, because we can
  2319 + * match consecutive blank lines with /\n+/ instead of something
  2320 + * contorted like /[ \t]*\n+/
  2321 + */
  2322 +showdown.subParser('stripBlankLines', function (text) {
  2323 + 'use strict';
  2324 + return text.replace(/^[ \t]+$/mg, '');
  2325 +});
  2326 +
  2327 +/**
  2328 + * Strips link definitions from text, stores the URLs and titles in
  2329 + * hash references.
  2330 + * Link defs are in the form: ^[id]: url "optional title"
  2331 + *
  2332 + * ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
  2333 + * [ \t]*
  2334 + * \n? // maybe *one* newline
  2335 + * [ \t]*
  2336 + * <?(\S+?)>? // url = $2
  2337 + * [ \t]*
  2338 + * \n? // maybe one newline
  2339 + * [ \t]*
  2340 + * (?:
  2341 + * (\n*) // any lines skipped = $3 attacklab: lookbehind removed
  2342 + * ["(]
  2343 + * (.+?) // title = $4
  2344 + * [")]
  2345 + * [ \t]*
  2346 + * )? // title is optional
  2347 + * (?:\n+|$)
  2348 + * /gm,
  2349 + * function(){...});
  2350 + *
  2351 + */
  2352 +showdown.subParser('stripLinkDefinitions', function (text, options, globals) {
  2353 + 'use strict';
  2354 +
  2355 + var regex = /^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=~0))/gm;
  2356 +
  2357 + // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  2358 + text += '~0';
  2359 +
  2360 + text = text.replace(regex, function (wholeMatch, linkId, url, width, height, blankLines, title) {
  2361 + linkId = linkId.toLowerCase();
  2362 + globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url); // Link IDs are case-insensitive
  2363 +
  2364 + if (blankLines) {
  2365 + // Oops, found blank lines, so it's not a title.
  2366 + // Put back the parenthetical statement we stole.
  2367 + return blankLines + title;
  2368 +
  2369 + } else {
  2370 + if (title) {
  2371 + globals.gTitles[linkId] = title.replace(/"|'/g, '&quot;');
  2372 + }
  2373 + if (options.parseImgDimensions && width && height) {
  2374 + globals.gDimensions[linkId] = {
  2375 + width: width,
  2376 + height: height
  2377 + };
  2378 + }
  2379 + }
  2380 + // Completely remove the definition from the text
  2381 + return '';
  2382 + });
  2383 +
  2384 + // attacklab: strip sentinel
  2385 + text = text.replace(/~0/, '');
  2386 +
  2387 + return text;
  2388 +});
  2389 +
  2390 +showdown.subParser('tables', function (text, options, globals) {
  2391 + 'use strict';
  2392 +
  2393 + if (!options.tables) {
  2394 + return text;
  2395 + }
  2396 +
  2397 + var tableRgx = /^[ \t]{0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|~0)/gm;
  2398 +
  2399 + function parseStyles(sLine) {
  2400 + if (/^:[ \t]*--*$/.test(sLine)) {
  2401 + return ' style="text-align:left;"';
  2402 + } else if (/^--*[ \t]*:[ \t]*$/.test(sLine)) {
  2403 + return ' style="text-align:right;"';
  2404 + } else if (/^:[ \t]*--*[ \t]*:$/.test(sLine)) {
  2405 + return ' style="text-align:center;"';
  2406 + } else {
  2407 + return '';
  2408 + }
  2409 + }
  2410 +
  2411 + function parseHeaders(header, style) {
  2412 + var id = '';
  2413 + header = header.trim();
  2414 + if (options.tableHeaderId) {
  2415 + id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"';
  2416 + }
  2417 + header = showdown.subParser('spanGamut')(header, options, globals);
  2418 +
  2419 + return '<th' + id + style + '>' + header + '</th>\n';
  2420 + }
  2421 +
  2422 + function parseCells(cell, style) {
  2423 + var subText = showdown.subParser('spanGamut')(cell, options, globals);
  2424 + return '<td' + style + '>' + subText + '</td>\n';
  2425 + }
  2426 +
  2427 + function buildTable(headers, cells) {
  2428 + var tb = '<table>\n<thead>\n<tr>\n',
  2429 + tblLgn = headers.length;
  2430 +
  2431 + for (var i = 0; i < tblLgn; ++i) {
  2432 + tb += headers[i];
  2433 + }
  2434 + tb += '</tr>\n</thead>\n<tbody>\n';
  2435 +
  2436 + for (i = 0; i < cells.length; ++i) {
  2437 + tb += '<tr>\n';
  2438 + for (var ii = 0; ii < tblLgn; ++ii) {
  2439 + tb += cells[i][ii];
  2440 + }
  2441 + tb += '</tr>\n';
  2442 + }
  2443 + tb += '</tbody>\n</table>\n';
  2444 + return tb;
  2445 + }
  2446 +
  2447 + text = globals.converter._dispatch('tables.before', text, options, globals);
  2448 +
  2449 + text = text.replace(tableRgx, function (rawTable) {
  2450 +
  2451 + var i, tableLines = rawTable.split('\n');
  2452 +
  2453 + // strip wrong first and last column if wrapped tables are used
  2454 + for (i = 0; i < tableLines.length; ++i) {
  2455 + if (/^[ \t]{0,3}\|/.test(tableLines[i])) {
  2456 + tableLines[i] = tableLines[i].replace(/^[ \t]{0,3}\|/, '');
  2457 + }
  2458 + if (/\|[ \t]*$/.test(tableLines[i])) {
  2459 + tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '');
  2460 + }
  2461 + }
  2462 +
  2463 + var rawHeaders = tableLines[0].split('|').map(function (s) { return s.trim();}),
  2464 + rawStyles = tableLines[1].split('|').map(function (s) { return s.trim();}),
  2465 + rawCells = [],
  2466 + headers = [],
  2467 + styles = [],
  2468 + cells = [];
  2469 +
  2470 + tableLines.shift();
  2471 + tableLines.shift();
  2472 +
  2473 + for (i = 0; i < tableLines.length; ++i) {
  2474 + if (tableLines[i].trim() === '') {
  2475 + continue;
  2476 + }
  2477 + rawCells.push(
  2478 + tableLines[i]
  2479 + .split('|')
  2480 + .map(function (s) {
  2481 + return s.trim();
  2482 + })
  2483 + );
  2484 + }
  2485 +
  2486 + if (rawHeaders.length < rawStyles.length) {
  2487 + return rawTable;
  2488 + }
  2489 +
  2490 + for (i = 0; i < rawStyles.length; ++i) {
  2491 + styles.push(parseStyles(rawStyles[i]));
  2492 + }
  2493 +
  2494 + for (i = 0; i < rawHeaders.length; ++i) {
  2495 + if (showdown.helper.isUndefined(styles[i])) {
  2496 + styles[i] = '';
  2497 + }
  2498 + headers.push(parseHeaders(rawHeaders[i], styles[i]));
  2499 + }
  2500 +
  2501 + for (i = 0; i < rawCells.length; ++i) {
  2502 + var row = [];
  2503 + for (var ii = 0; ii < headers.length; ++ii) {
  2504 + if (showdown.helper.isUndefined(rawCells[i][ii])) {
  2505 +
  2506 + }
  2507 + row.push(parseCells(rawCells[i][ii], styles[ii]));
  2508 + }
  2509 + cells.push(row);
  2510 + }
  2511 +
  2512 + return buildTable(headers, cells);
  2513 + });
  2514 +
  2515 + text = globals.converter._dispatch('tables.after', text, options, globals);
  2516 +
  2517 + return text;
  2518 +});
  2519 +
  2520 +/**
  2521 + * Swap back in all the special characters we've hidden.
  2522 + */
  2523 +showdown.subParser('unescapeSpecialChars', function (text) {
  2524 + 'use strict';
  2525 +
  2526 + text = text.replace(/~E(\d+)E/g, function (wholeMatch, m1) {
  2527 + var charCodeToReplace = parseInt(m1);
  2528 + return String.fromCharCode(charCodeToReplace);
  2529 + });
  2530 + return text;
  2531 +});
  2532 +module.exports = showdown;
  1 +// HTML 支持的数学符号
  2 +function strNumDiscode(str){
  3 + str = str.replace(/&forall;/g, '∀');
  4 + str = str.replace(/&part;/g, '∂');
  5 + str = str.replace(/&exists;/g, '∃');
  6 + str = str.replace(/&empty;/g, '∅');
  7 + str = str.replace(/&nabla;/g, '∇');
  8 + str = str.replace(/&isin;/g, '∈');
  9 + str = str.replace(/&notin;/g, '∉');
  10 + str = str.replace(/&ni;/g, '∋');
  11 + str = str.replace(/&prod;/g, '∏');
  12 + str = str.replace(/&sum;/g, '∑');
  13 + str = str.replace(/&minus;/g, '−');
  14 + str = str.replace(/&lowast;/g, '∗');
  15 + str = str.replace(/&radic;/g, '√');
  16 + str = str.replace(/&prop;/g, '∝');
  17 + str = str.replace(/&infin;/g, '∞');
  18 + str = str.replace(/&ang;/g, '∠');
  19 + str = str.replace(/&and;/g, '∧');
  20 + str = str.replace(/&or;/g, '∨');
  21 + str = str.replace(/&cap;/g, '∩');
  22 + str = str.replace(/&cap;/g, '∪');
  23 + str = str.replace(/&int;/g, '∫');
  24 + str = str.replace(/&there4;/g, '∴');
  25 + str = str.replace(/&sim;/g, '∼');
  26 + str = str.replace(/&cong;/g, '≅');
  27 + str = str.replace(/&asymp;/g, '≈');
  28 + str = str.replace(/&ne;/g, '≠');
  29 + str = str.replace(/&le;/g, '≤');
  30 + str = str.replace(/&ge;/g, '≥');
  31 + str = str.replace(/&sub;/g, '⊂');
  32 + str = str.replace(/&sup;/g, '⊃');
  33 + str = str.replace(/&nsub;/g, '⊄');
  34 + str = str.replace(/&sube;/g, '⊆');
  35 + str = str.replace(/&supe;/g, '⊇');
  36 + str = str.replace(/&oplus;/g, '⊕');
  37 + str = str.replace(/&otimes;/g, '⊗');
  38 + str = str.replace(/&perp;/g, '⊥');
  39 + str = str.replace(/&sdot;/g, '⋅');
  40 + return str;
  41 +}
  42 +
  43 +//HTML 支持的希腊字母
  44 +function strGreeceDiscode(str){
  45 + str = str.replace(/&Alpha;/g, 'Α');
  46 + str = str.replace(/&Beta;/g, 'Β');
  47 + str = str.replace(/&Gamma;/g, 'Γ');
  48 + str = str.replace(/&Delta;/g, 'Δ');
  49 + str = str.replace(/&Epsilon;/g, 'Ε');
  50 + str = str.replace(/&Zeta;/g, 'Ζ');
  51 + str = str.replace(/&Eta;/g, 'Η');
  52 + str = str.replace(/&Theta;/g, 'Θ');
  53 + str = str.replace(/&Iota;/g, 'Ι');
  54 + str = str.replace(/&Kappa;/g, 'Κ');
  55 + str = str.replace(/&Lambda;/g, 'Λ');
  56 + str = str.replace(/&Mu;/g, 'Μ');
  57 + str = str.replace(/&Nu;/g, 'Ν');
  58 + str = str.replace(/&Xi;/g, 'Ν');
  59 + str = str.replace(/&Omicron;/g, 'Ο');
  60 + str = str.replace(/&Pi;/g, 'Π');
  61 + str = str.replace(/&Rho;/g, 'Ρ');
  62 + str = str.replace(/&Sigma;/g, 'Σ');
  63 + str = str.replace(/&Tau;/g, 'Τ');
  64 + str = str.replace(/&Upsilon;/g, 'Υ');
  65 + str = str.replace(/&Phi;/g, 'Φ');
  66 + str = str.replace(/&Chi;/g, 'Χ');
  67 + str = str.replace(/&Psi;/g, 'Ψ');
  68 + str = str.replace(/&Omega;/g, 'Ω');
  69 +
  70 + str = str.replace(/&alpha;/g, 'α');
  71 + str = str.replace(/&beta;/g, 'β');
  72 + str = str.replace(/&gamma;/g, 'γ');
  73 + str = str.replace(/&delta;/g, 'δ');
  74 + str = str.replace(/&epsilon;/g, 'ε');
  75 + str = str.replace(/&zeta;/g, 'ζ');
  76 + str = str.replace(/&eta;/g, 'η');
  77 + str = str.replace(/&theta;/g, 'θ');
  78 + str = str.replace(/&iota;/g, 'ι');
  79 + str = str.replace(/&kappa;/g, 'κ');
  80 + str = str.replace(/&lambda;/g, 'λ');
  81 + str = str.replace(/&mu;/g, 'μ');
  82 + str = str.replace(/&nu;/g, 'ν');
  83 + str = str.replace(/&xi;/g, 'ξ');
  84 + str = str.replace(/&omicron;/g, 'ο');
  85 + str = str.replace(/&pi;/g, 'π');
  86 + str = str.replace(/&rho;/g, 'ρ');
  87 + str = str.replace(/&sigmaf;/g, 'ς');
  88 + str = str.replace(/&sigma;/g, 'σ');
  89 + str = str.replace(/&tau;/g, 'τ');
  90 + str = str.replace(/&upsilon;/g, 'υ');
  91 + str = str.replace(/&phi;/g, 'φ');
  92 + str = str.replace(/&chi;/g, 'χ');
  93 + str = str.replace(/&psi;/g, 'ψ');
  94 + str = str.replace(/&omega;/g, 'ω');
  95 + str = str.replace(/&thetasym;/g, 'ϑ');
  96 + str = str.replace(/&upsih;/g, 'ϒ');
  97 + str = str.replace(/&piv;/g, 'ϖ');
  98 + str = str.replace(/&middot;/g, '·');
  99 + return str;
  100 +}
  101 +
  102 +//
  103 +
  104 +function strcharacterDiscode(str){
  105 + // 加入常用解析
  106 + str = str.replace(/↵;/g, '\n');
  107 + str = str.replace(/&nbsp;/g, ' ');
  108 + str = str.replace(/&quot;/g, "'");
  109 + str = str.replace(/&amp;/g, '&');
  110 + // str = str.replace(/&lt;/g, '‹');
  111 + // str = str.replace(/&gt;/g, '›');
  112 +
  113 + str = str.replace(/&lt;/g, '<');
  114 + str = str.replace(/&gt;/g, '>');
  115 + str = str.replace(/&#8226;/g, '•');
  116 +
  117 + return str;
  118 +}
  119 +
  120 +// HTML 支持的其他实体
  121 +function strOtherDiscode(str){
  122 + str = str.replace(/&OElig;/g, 'Œ');
  123 + str = str.replace(/&oelig;/g, 'œ');
  124 + str = str.replace(/&Scaron;/g, 'Š');
  125 + str = str.replace(/&scaron;/g, 'š');
  126 + str = str.replace(/&Yuml;/g, 'Ÿ');
  127 + str = str.replace(/&fnof;/g, 'ƒ');
  128 + str = str.replace(/&circ;/g, 'ˆ');
  129 + str = str.replace(/&tilde;/g, '˜');
  130 + str = str.replace(/&ensp;/g, '');
  131 + str = str.replace(/&emsp;/g, '');
  132 + str = str.replace(/&thinsp;/g, '');
  133 + str = str.replace(/&zwnj;/g, '');
  134 + str = str.replace(/&zwj;/g, '');
  135 + str = str.replace(/&lrm;/g, '');
  136 + str = str.replace(/&rlm;/g, '');
  137 + str = str.replace(/&ndash;/g, '–');
  138 + str = str.replace(/&mdash;/g, '—');
  139 + str = str.replace(/&lsquo;/g, '‘');
  140 + str = str.replace(/&rsquo;/g, '’');
  141 + str = str.replace(/&sbquo;/g, '‚');
  142 + str = str.replace(/&ldquo;/g, '“');
  143 + str = str.replace(/&rdquo;/g, '”');
  144 + str = str.replace(/&bdquo;/g, '„');
  145 + str = str.replace(/&dagger;/g, '†');
  146 + str = str.replace(/&Dagger;/g, '‡');
  147 + str = str.replace(/&bull;/g, '•');
  148 + str = str.replace(/&hellip;/g, '…');
  149 + str = str.replace(/&permil;/g, '‰');
  150 + str = str.replace(/&prime;/g, '′');
  151 + str = str.replace(/&Prime;/g, '″');
  152 + str = str.replace(/&lsaquo;/g, '‹');
  153 + str = str.replace(/&rsaquo;/g, '›');
  154 + str = str.replace(/&oline;/g, '‾');
  155 + str = str.replace(/&euro;/g, '€');
  156 + str = str.replace(/&trade;/g, '™');
  157 +
  158 + str = str.replace(/&larr;/g, '←');
  159 + str = str.replace(/&uarr;/g, '↑');
  160 + str = str.replace(/&rarr;/g, '→');
  161 + str = str.replace(/&darr;/g, '↓');
  162 + str = str.replace(/&harr;/g, '↔');
  163 + str = str.replace(/&crarr;/g, '↵');
  164 + str = str.replace(/&lceil;/g, '⌈');
  165 + str = str.replace(/&rceil;/g, '⌉');
  166 +
  167 + str = str.replace(/&lfloor;/g, '⌊');
  168 + str = str.replace(/&rfloor;/g, '⌋');
  169 + str = str.replace(/&loz;/g, '◊');
  170 + str = str.replace(/&spades;/g, '♠');
  171 + str = str.replace(/&clubs;/g, '♣');
  172 + str = str.replace(/&hearts;/g, '♥');
  173 +
  174 + str = str.replace(/&diams;/g, '♦');
  175 + str = str.replace(/&#39;/g, '\'');
  176 + return str;
  177 +}
  178 +
  179 +function strMoreDiscode(str){
  180 + //str = str.replace(/\r\n/g,"");
  181 + //str = str.replace(/\n/g,"");
  182 +
  183 + str = str.replace(/code/g,"wxxxcode-style");
  184 + return str;
  185 +}
  186 +
  187 +function strDiscode(str){
  188 + str = strNumDiscode(str);
  189 + str = strGreeceDiscode(str);
  190 + str = strcharacterDiscode(str);
  191 + str = strOtherDiscode(str);
  192 + str = strMoreDiscode(str);
  193 + return str;
  194 +}
  195 +function urlToHttpUrl(url,rep){
  196 +
  197 + var patt1 = new RegExp("^//");
  198 + var result = patt1.test(url);
  199 + if(result){
  200 + url = rep+":"+url;
  201 + }
  202 + return url;
  203 +}
  204 +
  205 +module.exports = {
  206 + strDiscode:strDiscode,
  207 + urlToHttpUrl:urlToHttpUrl
  208 +}
  1 +/**
  2 + * author: Di (微信小程序开发工程师)
  3 + * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
  4 + * 垂直微信小程序开发交流社区
  5 + *
  6 + * github地址: https://github.com/icindy/wxParse
  7 + *
  8 + * for: 微信小程序富文本解析
  9 + * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
  10 + */
  11 +
  12 +/**
  13 + * utils函数引入
  14 + **/
  15 +import showdown from './showdown.js';
  16 +import HtmlToJson from './html2json.js';
  17 +/**
  18 + * 配置及公有属性
  19 + **/
  20 +var realWindowWidth = 0;
  21 +var realWindowHeight = 0;
  22 +wx.getSystemInfo({
  23 + success: function (res) {
  24 + realWindowWidth = res.windowWidth
  25 + realWindowHeight = res.windowHeight
  26 + }
  27 +})
  28 +/**
  29 + * 主函数入口区
  30 + **/
  31 +function wxParse(bindName = 'wxParseData', type='html', data='<div class="color:red;">数据不能为空</div>', target,imagePadding) {
  32 + var that = target;
  33 + var transData = {};//存放转化后的数据
  34 + if (type == 'html') {
  35 + transData = HtmlToJson.html2json(data, bindName);
  36 + // console.log(JSON.stringify(transData, ' ', ' '));
  37 + } else if (type == 'md' || type == 'markdown') {
  38 + var converter = new showdown.Converter();
  39 + var html = converter.makeHtml(data);
  40 + transData = HtmlToJson.html2json(html, bindName);
  41 + // console.log(JSON.stringify(transData, ' ', ' '));
  42 + }
  43 + transData.view = {};
  44 + transData.view.imagePadding = 0;
  45 + if(typeof(imagePadding) != 'undefined'){
  46 + transData.view.imagePadding = imagePadding
  47 + }
  48 + var bindData = {};
  49 + bindData[bindName] = transData;
  50 + that.setData(bindData)
  51 + that.wxParseImgLoad = wxParseImgLoad;
  52 + that.wxParseImgTap = wxParseImgTap;
  53 +}
  54 +// 图片点击事件
  55 +function wxParseImgTap(e) {
  56 + var that = this;
  57 + var nowImgUrl = e.target.dataset.src;
  58 + var tagFrom = e.target.dataset.from;
  59 + if (typeof (tagFrom) != 'undefined' && tagFrom.length > 0) {
  60 + wx.previewImage({
  61 + current: nowImgUrl, // 当前显示图片的http链接
  62 + urls: that.data[tagFrom].imageUrls // 需要预览的图片http链接列表
  63 + })
  64 + }
  65 +}
  66 +
  67 +/**
  68 + * 图片视觉宽高计算函数区
  69 + **/
  70 +function wxParseImgLoad(e) {
  71 + var that = this;
  72 + var tagFrom = e.target.dataset.from;
  73 + var idx = e.target.dataset.idx;
  74 + if (typeof (tagFrom) != 'undefined' && tagFrom.length > 0) {
  75 + calMoreImageInfo(e, idx, that, tagFrom)
  76 + }
  77 +}
  78 +// 假循环获取计算图片视觉最佳宽高
  79 +function calMoreImageInfo(e, idx, that, bindName) {
  80 + var temData = that.data[bindName];
  81 + if (!temData || temData.images.length == 0) {
  82 + return;
  83 + }
  84 + var temImages = temData.images;
  85 + //因为无法获取view宽度 需要自定义padding进行计算,稍后处理
  86 + var recal = wxAutoImageCal(e.detail.width, e.detail.height,that,bindName);
  87 + // temImages[idx].width = recal.imageWidth;
  88 + // temImages[idx].height = recal.imageheight;
  89 + // temData.images = temImages;
  90 + // var bindData = {};
  91 + // bindData[bindName] = temData;
  92 + // that.setData(bindData);
  93 + var index = temImages[idx].index
  94 + var key = `${bindName}`
  95 + for (var i of index.split('.')) key+=`.nodes[${i}]`
  96 + var keyW = key + '.width'
  97 + var keyH = key + '.height'
  98 + that.setData({
  99 + [keyW]: recal.imageWidth,
  100 + [keyH]: recal.imageheight,
  101 + })
  102 +}
  103 +
  104 +// 计算视觉优先的图片宽高
  105 +function wxAutoImageCal(originalWidth, originalHeight,that,bindName) {
  106 + //获取图片的原始长宽
  107 + var windowWidth = 0, windowHeight = 0;
  108 + var autoWidth = 0, autoHeight = 0;
  109 + var results = {};
  110 + var padding = that.data[bindName].view.imagePadding;
  111 + windowWidth = realWindowWidth-2*padding;
  112 + windowHeight = realWindowHeight;
  113 + //判断按照那种方式进行缩放
  114 + // console.log("windowWidth" + windowWidth);
  115 + if (originalWidth > windowWidth) {//在图片width大于手机屏幕width时候
  116 + autoWidth = windowWidth;
  117 + // console.log("autoWidth" + autoWidth);
  118 + autoHeight = (autoWidth * originalHeight) / originalWidth;
  119 + // console.log("autoHeight" + autoHeight);
  120 + results.imageWidth = autoWidth;
  121 + results.imageheight = autoHeight;
  122 + } else {//否则展示原来的数据
  123 + results.imageWidth = originalWidth;
  124 + results.imageheight = originalHeight;
  125 + }
  126 + return results;
  127 +}
  128 +
  129 +function wxParseTemArray(temArrayName,bindNameReg,total,that){
  130 + var array = [];
  131 + var temData = that.data;
  132 + var obj = null;
  133 + for(var i = 0; i < total; i++){
  134 + var simArr = temData[bindNameReg+i].nodes;
  135 + array.push(simArr);
  136 + }
  137 +
  138 + temArrayName = temArrayName || 'wxParseTemArray';
  139 + obj = JSON.parse('{"'+ temArrayName +'":""}');
  140 + obj[temArrayName] = array;
  141 + that.setData(obj);
  142 +}
  143 +
  144 +/**
  145 + * 配置emojis
  146 + *
  147 + */
  148 +
  149 +function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){
  150 + HtmlToJson.emojisInit(reg,baseSrc,emojis);
  151 +}
  152 +
  153 +module.exports = {
  154 + wxParse: wxParse,
  155 + wxParseTemArray:wxParseTemArray,
  156 + emojisInit:emojisInit
  157 +}
  158 +
  159 +
  1 +<!--**
  2 + * author: Di (微信小程序开发工程师)
  3 + * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
  4 + * 垂直微信小程序开发交流社区
  5 + *
  6 + * github地址: https://github.com/icindy/wxParse
  7 + *
  8 + * for: 微信小程序富文本解析
  9 + * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
  10 + */-->
  11 +
  12 +<!--基础元素-->
  13 +<template name="wxParseVideo">
  14 + <!--增加video标签支持,并循环添加-->
  15 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  16 + <video class="{{item.classStr}} wxParse-{{item.tag}}-video" src="{{item.attr.src}}"></video>
  17 + </view>
  18 +</template>
  19 +
  20 +<template name="wxParseImg">
  21 + <image class="{{item.classStr}} wxParse-{{item.tag}}" data-from="{{item.from}}" data-src="{{item.attr.src}}" data-idx="{{item.imgIndex}}" src="{{item.attr.src}}" mode="aspectFit" bindload="wxParseImgLoad" bindtap="wxParseImgTap" mode="widthFix" style="width:{{item.width}}px;"
  22 + />
  23 +</template>
  24 +
  25 +<template name="WxEmojiView">
  26 + <view class="WxEmojiView wxParse-inline" style="{{item.styleStr}}">
  27 + <block wx:for="{{item.textArray}}" wx:key="">
  28 + <block class="{{item.text == '\\n' ? 'wxParse-hide':''}}" wx:if="{{item.node == 'text'}}">{{item.text}}</block>
  29 + <block wx:elif="{{item.node == 'element'}}">
  30 + <image class="wxEmoji" src="{{item.baseSrc}}{{item.text}}" />
  31 + </block>
  32 + </block>
  33 + </view>
  34 +</template>
  35 +
  36 +<template name="WxParseBr">
  37 + <text>\n</text>
  38 +</template>
  39 +<!--入口模版-->
  40 +
  41 +<template name="wxParse">
  42 + <block wx:for="{{wxParseData}}" wx:key="">
  43 + <template is="wxParse0" data="{{item}}" />
  44 + </block>
  45 +</template>
  46 +
  47 +
  48 +<!--循环模版-->
  49 +<template name="wxParse0">
  50 + <!--<template is="wxParse1" data="{{item}}" />-->
  51 + <!--判断是否是标签节点-->
  52 + <block wx:if="{{item.node == 'element'}}">
  53 + <block wx:if="{{item.tag == 'button'}}">
  54 + <button type="default" size="mini">
  55 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  56 + <template is="wxParse1" data="{{item}}" />
  57 + </block>
  58 + </button>
  59 + </block>
  60 + <!--li类型-->
  61 + <block wx:elif="{{item.tag == 'li'}}">
  62 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  63 + <view class="{{item.classStr}} wxParse-li-inner">
  64 + <view class="{{item.classStr}} wxParse-li-text">
  65 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  66 + </view>
  67 + <view class="{{item.classStr}} wxParse-li-text">
  68 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  69 + <template is="wxParse1" data="{{item}}" />
  70 + </block>
  71 + </view>
  72 + </view>
  73 + </view>
  74 + </block>
  75 +
  76 + <!--video类型-->
  77 + <block wx:elif="{{item.tag == 'video'}}">
  78 + <template is="wxParseVideo" data="{{item}}" />
  79 + </block>
  80 +
  81 + <!--img类型-->
  82 + <block wx:elif="{{item.tag == 'img'}}">
  83 + <template is="wxParseImg" data="{{item}}" />
  84 + </block>
  85 +
  86 + <!--a类型-->
  87 + <block wx:elif="{{item.tag == 'a'}}">
  88 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  89 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  90 + <template is="wxParse1" data="{{item}}" />
  91 + </block>
  92 + </view>
  93 + </block>
  94 + <block wx:elif="{{item.tag == 'table'}}">
  95 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  96 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  97 + <template is="wxParse1" data="{{item}}" />
  98 + </block>
  99 + </view>
  100 + </block>
  101 +
  102 + <block wx:elif="{{item.tag == 'br'}}">
  103 + <template is="WxParseBr"></template>
  104 + </block>
  105 + <!--其他块级标签-->
  106 + <block wx:elif="{{item.tagType == 'block'}}">
  107 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  108 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  109 + <template is="wxParse1" data="{{item}}" />
  110 + </block>
  111 + </view>
  112 + </block>
  113 +
  114 + <!--内联标签-->
  115 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  116 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  117 + <template is="wxParse1" data="{{item}}" />
  118 + </block>
  119 + </view>
  120 +
  121 + </block>
  122 +
  123 + <!--判断是否是文本节点-->
  124 + <block wx:elif="{{item.node == 'text'}}">
  125 + <!--如果是,直接进行-->
  126 + <template is="WxEmojiView" data="{{item}}" />
  127 + </block>
  128 +
  129 +</template>
  130 +
  131 +
  132 +
  133 +<!--循环模版-->
  134 +<template name="wxParse1">
  135 + <!--<template is="wxParse2" data="{{item}}" />-->
  136 + <!--判断是否是标签节点-->
  137 + <block wx:if="{{item.node == 'element'}}">
  138 + <block wx:if="{{item.tag == 'button'}}">
  139 + <button type="default" size="mini">
  140 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  141 + <template is="wxParse2" data="{{item}}" />
  142 + </block>
  143 + </button>
  144 + </block>
  145 + <!--li类型-->
  146 + <block wx:elif="{{item.tag == 'li'}}">
  147 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  148 + <view class="{{item.classStr}} wxParse-li-inner">
  149 + <view class="{{item.classStr}} wxParse-li-text">
  150 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  151 + </view>
  152 + <view class="{{item.classStr}} wxParse-li-text">
  153 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  154 + <template is="wxParse2" data="{{item}}" />
  155 + </block>
  156 + </view>
  157 + </view>
  158 + </view>
  159 + </block>
  160 +
  161 + <!--video类型-->
  162 + <block wx:elif="{{item.tag == 'video'}}">
  163 + <template is="wxParseVideo" data="{{item}}" />
  164 + </block>
  165 +
  166 + <!--img类型-->
  167 + <block wx:elif="{{item.tag == 'img'}}">
  168 + <template is="wxParseImg" data="{{item}}" />
  169 + </block>
  170 +
  171 + <!--a类型-->
  172 + <block wx:elif="{{item.tag == 'a'}}">
  173 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  174 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  175 + <template is="wxParse2" data="{{item}}" />
  176 + </block>
  177 + </view>
  178 + </block>
  179 +
  180 + <block wx:elif="{{item.tag == 'br'}}">
  181 + <template is="WxParseBr"></template>
  182 + </block>
  183 + <!--其他块级标签-->
  184 + <block wx:elif="{{item.tagType == 'block'}}">
  185 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  186 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  187 + <template is="wxParse2" data="{{item}}" />
  188 + </block>
  189 + </view>
  190 + </block>
  191 +
  192 + <!--内联标签-->
  193 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  194 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  195 + <template is="wxParse2" data="{{item}}" />
  196 + </block>
  197 + </view>
  198 +
  199 + </block>
  200 +
  201 + <!--判断是否是文本节点-->
  202 + <block wx:elif="{{item.node == 'text'}}">
  203 + <!--如果是,直接进行-->
  204 + <template is="WxEmojiView" data="{{item}}" />
  205 + </block>
  206 +
  207 +</template>
  208 +
  209 +
  210 +<!--循环模版-->
  211 +<template name="wxParse2">
  212 + <!--<template is="wxParse3" data="{{item}}" />-->
  213 + <!--判断是否是标签节点-->
  214 + <block wx:if="{{item.node == 'element'}}">
  215 + <block wx:if="{{item.tag == 'button'}}">
  216 + <button type="default" size="mini">
  217 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  218 + <template is="wxParse3" data="{{item}}" />
  219 + </block>
  220 + </button>
  221 + </block>
  222 + <!--li类型-->
  223 + <block wx:elif="{{item.tag == 'li'}}">
  224 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  225 + <view class="{{item.classStr}} wxParse-li-inner">
  226 + <view class="{{item.classStr}} wxParse-li-text">
  227 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  228 + </view>
  229 + <view class="{{item.classStr}} wxParse-li-text">
  230 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  231 + <template is="wxParse3" data="{{item}}" />
  232 + </block>
  233 + </view>
  234 + </view>
  235 + </view>
  236 + </block>
  237 +
  238 + <!--video类型-->
  239 + <block wx:elif="{{item.tag == 'video'}}">
  240 + <template is="wxParseVideo" data="{{item}}" />
  241 + </block>
  242 +
  243 + <!--img类型-->
  244 + <block wx:elif="{{item.tag == 'img'}}">
  245 + <template is="wxParseImg" data="{{item}}" />
  246 + </block>
  247 +
  248 + <!--a类型-->
  249 + <block wx:elif="{{item.tag == 'a'}}">
  250 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  251 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  252 + <template is="wxParse3" data="{{item}}" />
  253 + </block>
  254 + </view>
  255 + </block>
  256 +
  257 + <block wx:elif="{{item.tag == 'br'}}">
  258 + <template is="WxParseBr"></template>
  259 + </block>
  260 + <!--其他块级标签-->
  261 + <block wx:elif="{{item.tagType == 'block'}}">
  262 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  263 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  264 + <template is="wxParse3" data="{{item}}" />
  265 + </block>
  266 + </view>
  267 + </block>
  268 +
  269 + <!--内联标签-->
  270 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  271 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  272 + <template is="wxParse3" data="{{item}}" />
  273 + </block>
  274 + </view>
  275 +
  276 + </block>
  277 +
  278 + <!--判断是否是文本节点-->
  279 + <block wx:elif="{{item.node == 'text'}}">
  280 + <!--如果是,直接进行-->
  281 + <template is="WxEmojiView" data="{{item}}" />
  282 + </block>
  283 +
  284 +</template>
  285 +
  286 +<!--循环模版-->
  287 +<template name="wxParse3">
  288 + <!--<template is="wxParse4" data="{{item}}" />-->
  289 + <!--判断是否是标签节点-->
  290 + <block wx:if="{{item.node == 'element'}}">
  291 + <block wx:if="{{item.tag == 'button'}}">
  292 + <button type="default" size="mini">
  293 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  294 + <template is="wxParse4" data="{{item}}" />
  295 + </block>
  296 + </button>
  297 + </block>
  298 + <!--li类型-->
  299 + <block wx:elif="{{item.tag == 'li'}}">
  300 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  301 + <view class="{{item.classStr}} wxParse-li-inner">
  302 + <view class="{{item.classStr}} wxParse-li-text">
  303 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  304 + </view>
  305 + <view class="{{item.classStr}} wxParse-li-text">
  306 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  307 + <template is="wxParse4" data="{{item}}" />
  308 + </block>
  309 + </view>
  310 + </view>
  311 + </view>
  312 + </block>
  313 +
  314 + <!--video类型-->
  315 + <block wx:elif="{{item.tag == 'video'}}">
  316 + <template is="wxParseVideo" data="{{item}}" />
  317 + </block>
  318 +
  319 + <!--img类型-->
  320 + <block wx:elif="{{item.tag == 'img'}}">
  321 + <template is="wxParseImg" data="{{item}}" />
  322 + </block>
  323 +
  324 + <!--a类型-->
  325 + <block wx:elif="{{item.tag == 'a'}}">
  326 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  327 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  328 + <template is="wxParse4" data="{{item}}" />
  329 + </block>
  330 + </view>
  331 + </block>
  332 +
  333 + <block wx:elif="{{item.tag == 'br'}}">
  334 + <template is="WxParseBr"></template>
  335 + </block>
  336 + <!--其他块级标签-->
  337 + <block wx:elif="{{item.tagType == 'block'}}">
  338 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  339 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  340 + <template is="wxParse4" data="{{item}}" />
  341 + </block>
  342 + </view>
  343 + </block>
  344 +
  345 + <!--内联标签-->
  346 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  347 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  348 + <template is="wxParse4" data="{{item}}" />
  349 + </block>
  350 + </view>
  351 +
  352 + </block>
  353 +
  354 + <!--判断是否是文本节点-->
  355 + <block wx:elif="{{item.node == 'text'}}">
  356 + <!--如果是,直接进行-->
  357 + <template is="WxEmojiView" data="{{item}}" />
  358 + </block>
  359 +
  360 +</template>
  361 +
  362 +<!--循环模版-->
  363 +<template name="wxParse4">
  364 + <!--<template is="wxParse5" data="{{item}}" />-->
  365 + <!--判断是否是标签节点-->
  366 + <block wx:if="{{item.node == 'element'}}">
  367 + <block wx:if="{{item.tag == 'button'}}">
  368 + <button type="default" size="mini">
  369 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  370 + <template is="wxParse5" data="{{item}}" />
  371 + </block>
  372 + </button>
  373 + </block>
  374 + <!--li类型-->
  375 + <block wx:elif="{{item.tag == 'li'}}">
  376 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  377 + <view class="{{item.classStr}} wxParse-li-inner">
  378 + <view class="{{item.classStr}} wxParse-li-text">
  379 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  380 + </view>
  381 + <view class="{{item.classStr}} wxParse-li-text">
  382 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  383 + <template is="wxParse5" data="{{item}}" />
  384 + </block>
  385 + </view>
  386 + </view>
  387 + </view>
  388 + </block>
  389 +
  390 + <!--video类型-->
  391 + <block wx:elif="{{item.tag == 'video'}}">
  392 + <template is="wxParseVideo" data="{{item}}" />
  393 + </block>
  394 +
  395 + <!--img类型-->
  396 + <block wx:elif="{{item.tag == 'img'}}">
  397 + <template is="wxParseImg" data="{{item}}" />
  398 + </block>
  399 +
  400 + <!--a类型-->
  401 + <block wx:elif="{{item.tag == 'a'}}">
  402 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  403 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  404 + <template is="wxParse5" data="{{item}}" />
  405 + </block>
  406 + </view>
  407 + </block>
  408 +
  409 + <block wx:elif="{{item.tag == 'br'}}">
  410 + <template is="WxParseBr"></template>
  411 + </block>
  412 + <!--其他块级标签-->
  413 + <block wx:elif="{{item.tagType == 'block'}}">
  414 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  415 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  416 + <template is="wxParse5" data="{{item}}" />
  417 + </block>
  418 + </view>
  419 + </block>
  420 +
  421 + <!--内联标签-->
  422 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  423 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  424 + <template is="wxParse5" data="{{item}}" />
  425 + </block>
  426 + </view>
  427 +
  428 + </block>
  429 +
  430 + <!--判断是否是文本节点-->
  431 + <block wx:elif="{{item.node == 'text'}}">
  432 + <!--如果是,直接进行-->
  433 + <template is="WxEmojiView" data="{{item}}" />
  434 + </block>
  435 +
  436 +</template>
  437 +
  438 +<!--循环模版-->
  439 +<template name="wxParse5">
  440 + <!--<template is="wxParse6" data="{{item}}" />-->
  441 + <!--判断是否是标签节点-->
  442 + <block wx:if="{{item.node == 'element'}}">
  443 + <block wx:if="{{item.tag == 'button'}}">
  444 + <button type="default" size="mini">
  445 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  446 + <template is="wxParse6" data="{{item}}" />
  447 + </block>
  448 + </button>
  449 + </block>
  450 + <!--li类型-->
  451 + <block wx:elif="{{item.tag == 'li'}}">
  452 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  453 + <view class="{{item.classStr}} wxParse-li-inner">
  454 + <view class="{{item.classStr}} wxParse-li-text">
  455 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  456 + </view>
  457 + <view class="{{item.classStr}} wxParse-li-text">
  458 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  459 + <template is="wxParse6" data="{{item}}" />
  460 + </block>
  461 + </view>
  462 + </view>
  463 + </view>
  464 + </block>
  465 +
  466 + <!--video类型-->
  467 + <block wx:elif="{{item.tag == 'video'}}">
  468 + <template is="wxParseVideo" data="{{item}}" />
  469 + </block>
  470 +
  471 + <!--img类型-->
  472 + <block wx:elif="{{item.tag == 'img'}}">
  473 + <template is="wxParseImg" data="{{item}}" />
  474 + </block>
  475 +
  476 + <!--a类型-->
  477 + <block wx:elif="{{item.tag == 'a'}}">
  478 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  479 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  480 + <template is="wxParse6" data="{{item}}" />
  481 + </block>
  482 + </view>
  483 + </block>
  484 +
  485 + <block wx:elif="{{item.tag == 'br'}}">
  486 + <template is="WxParseBr"></template>
  487 + </block>
  488 + <!--其他块级标签-->
  489 + <block wx:elif="{{item.tagType == 'block'}}">
  490 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  491 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  492 + <template is="wxParse6" data="{{item}}" />
  493 + </block>
  494 + </view>
  495 + </block>
  496 +
  497 + <!--内联标签-->
  498 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  499 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  500 + <template is="wxParse6" data="{{item}}" />
  501 + </block>
  502 + </view>
  503 +
  504 + </block>
  505 +
  506 + <!--判断是否是文本节点-->
  507 + <block wx:elif="{{item.node == 'text'}}">
  508 + <!--如果是,直接进行-->
  509 + <template is="WxEmojiView" data="{{item}}" />
  510 + </block>
  511 +
  512 +</template>
  513 +
  514 +<!--循环模版-->
  515 +<template name="wxParse6">
  516 + <!--<template is="wxParse7" data="{{item}}" />-->
  517 + <!--判断是否是标签节点-->
  518 + <block wx:if="{{item.node == 'element'}}">
  519 + <block wx:if="{{item.tag == 'button'}}">
  520 + <button type="default" size="mini">
  521 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  522 + <template is="wxParse7" data="{{item}}" />
  523 + </block>
  524 + </button>
  525 + </block>
  526 + <!--li类型-->
  527 + <block wx:elif="{{item.tag == 'li'}}">
  528 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  529 + <view class="{{item.classStr}} wxParse-li-inner">
  530 + <view class="{{item.classStr}} wxParse-li-text">
  531 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  532 + </view>
  533 + <view class="{{item.classStr}} wxParse-li-text">
  534 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  535 + <template is="wxParse7" data="{{item}}" />
  536 + </block>
  537 + </view>
  538 + </view>
  539 + </view>
  540 + </block>
  541 +
  542 + <!--video类型-->
  543 + <block wx:elif="{{item.tag == 'video'}}">
  544 + <template is="wxParseVideo" data="{{item}}" />
  545 + </block>
  546 +
  547 + <!--img类型-->
  548 + <block wx:elif="{{item.tag == 'img'}}">
  549 + <template is="wxParseImg" data="{{item}}" />
  550 + </block>
  551 +
  552 + <!--a类型-->
  553 + <block wx:elif="{{item.tag == 'a'}}">
  554 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  555 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  556 + <template is="wxParse7" data="{{item}}" />
  557 + </block>
  558 + </view>
  559 + </block>
  560 +
  561 + <block wx:elif="{{item.tag == 'br'}}">
  562 + <template is="WxParseBr"></template>
  563 + </block>
  564 + <!--其他块级标签-->
  565 + <block wx:elif="{{item.tagType == 'block'}}">
  566 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  567 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  568 + <template is="wxParse7" data="{{item}}" />
  569 + </block>
  570 + </view>
  571 + </block>
  572 +
  573 + <!--内联标签-->
  574 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  575 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  576 + <template is="wxParse7" data="{{item}}" />
  577 + </block>
  578 + </view>
  579 +
  580 + </block>
  581 +
  582 + <!--判断是否是文本节点-->
  583 + <block wx:elif="{{item.node == 'text'}}">
  584 + <!--如果是,直接进行-->
  585 + <template is="WxEmojiView" data="{{item}}" />
  586 + </block>
  587 +
  588 +</template>
  589 +<!--循环模版-->
  590 +<template name="wxParse7">
  591 + <!--<template is="wxParse8" data="{{item}}" />-->
  592 + <!--判断是否是标签节点-->
  593 + <block wx:if="{{item.node == 'element'}}">
  594 + <block wx:if="{{item.tag == 'button'}}">
  595 + <button type="default" size="mini">
  596 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  597 + <template is="wxParse8" data="{{item}}" />
  598 + </block>
  599 + </button>
  600 + </block>
  601 + <!--li类型-->
  602 + <block wx:elif="{{item.tag == 'li'}}">
  603 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  604 + <view class="{{item.classStr}} wxParse-li-inner">
  605 + <view class="{{item.classStr}} wxParse-li-text">
  606 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  607 + </view>
  608 + <view class="{{item.classStr}} wxParse-li-text">
  609 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  610 + <template is="wxParse8" data="{{item}}" />
  611 + </block>
  612 + </view>
  613 + </view>
  614 + </view>
  615 + </block>
  616 +
  617 + <!--video类型-->
  618 + <block wx:elif="{{item.tag == 'video'}}">
  619 + <template is="wxParseVideo" data="{{item}}" />
  620 + </block>
  621 +
  622 + <!--img类型-->
  623 + <block wx:elif="{{item.tag == 'img'}}">
  624 + <template is="wxParseImg" data="{{item}}" />
  625 + </block>
  626 +
  627 + <!--a类型-->
  628 + <block wx:elif="{{item.tag == 'a'}}">
  629 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  630 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  631 + <template is="wxParse8" data="{{item}}" />
  632 + </block>
  633 + </view>
  634 + </block>
  635 +
  636 + <block wx:elif="{{item.tag == 'br'}}">
  637 + <template is="WxParseBr"></template>
  638 + </block>
  639 + <!--其他块级标签-->
  640 + <block wx:elif="{{item.tagType == 'block'}}">
  641 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  642 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  643 + <template is="wxParse8" data="{{item}}" />
  644 + </block>
  645 + </view>
  646 + </block>
  647 +
  648 + <!--内联标签-->
  649 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  650 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  651 + <template is="wxParse8" data="{{item}}" />
  652 + </block>
  653 + </view>
  654 +
  655 + </block>
  656 +
  657 + <!--判断是否是文本节点-->
  658 + <block wx:elif="{{item.node == 'text'}}">
  659 + <!--如果是,直接进行-->
  660 + <template is="WxEmojiView" data="{{item}}" />
  661 + </block>
  662 +
  663 +</template>
  664 +
  665 +<!--循环模版-->
  666 +<template name="wxParse8">
  667 + <!--<template is="wxParse9" data="{{item}}" />-->
  668 + <!--判断是否是标签节点-->
  669 + <block wx:if="{{item.node == 'element'}}">
  670 + <block wx:if="{{item.tag == 'button'}}">
  671 + <button type="default" size="mini">
  672 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  673 + <template is="wxParse9" data="{{item}}" />
  674 + </block>
  675 + </button>
  676 + </block>
  677 + <!--li类型-->
  678 + <block wx:elif="{{item.tag == 'li'}}">
  679 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  680 + <view class="{{item.classStr}} wxParse-li-inner">
  681 + <view class="{{item.classStr}} wxParse-li-text">
  682 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  683 + </view>
  684 + <view class="{{item.classStr}} wxParse-li-text">
  685 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  686 + <template is="wxParse9" data="{{item}}" />
  687 + </block>
  688 + </view>
  689 + </view>
  690 + </view>
  691 + </block>
  692 +
  693 + <!--video类型-->
  694 + <block wx:elif="{{item.tag == 'video'}}">
  695 + <template is="wxParseVideo" data="{{item}}" />
  696 + </block>
  697 +
  698 + <!--img类型-->
  699 + <block wx:elif="{{item.tag == 'img'}}">
  700 + <template is="wxParseImg" data="{{item}}" />
  701 + </block>
  702 +
  703 + <!--a类型-->
  704 + <block wx:elif="{{item.tag == 'a'}}">
  705 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  706 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  707 + <template is="wxParse9" data="{{item}}" />
  708 + </block>
  709 + </view>
  710 + </block>
  711 +
  712 + <block wx:elif="{{item.tag == 'br'}}">
  713 + <template is="WxParseBr"></template>
  714 + </block>
  715 + <!--其他块级标签-->
  716 + <block wx:elif="{{item.tagType == 'block'}}">
  717 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  718 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  719 + <template is="wxParse9" data="{{item}}" />
  720 + </block>
  721 + </view>
  722 + </block>
  723 +
  724 + <!--内联标签-->
  725 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  726 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  727 + <template is="wxParse9" data="{{item}}" />
  728 + </block>
  729 + </view>
  730 +
  731 + </block>
  732 +
  733 + <!--判断是否是文本节点-->
  734 + <block wx:elif="{{item.node == 'text'}}">
  735 + <!--如果是,直接进行-->
  736 + <template is="WxEmojiView" data="{{item}}" />
  737 + </block>
  738 +
  739 +</template>
  740 +
  741 +<!--循环模版-->
  742 +<template name="wxParse9">
  743 + <!--<template is="wxParse10" data="{{item}}" />-->
  744 + <!--判断是否是标签节点-->
  745 + <block wx:if="{{item.node == 'element'}}">
  746 + <block wx:if="{{item.tag == 'button'}}">
  747 + <button type="default" size="mini">
  748 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  749 + <template is="wxParse10" data="{{item}}" />
  750 + </block>
  751 + </button>
  752 + </block>
  753 + <!--li类型-->
  754 + <block wx:elif="{{item.tag == 'li'}}">
  755 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  756 + <view class="{{item.classStr}} wxParse-li-inner">
  757 + <view class="{{item.classStr}} wxParse-li-text">
  758 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  759 + </view>
  760 + <view class="{{item.classStr}} wxParse-li-text">
  761 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  762 + <template is="wxParse10" data="{{item}}" />
  763 + </block>
  764 + </view>
  765 + </view>
  766 + </view>
  767 + </block>
  768 +
  769 + <!--video类型-->
  770 + <block wx:elif="{{item.tag == 'video'}}">
  771 + <template is="wxParseVideo" data="{{item}}" />
  772 + </block>
  773 +
  774 + <!--img类型-->
  775 + <block wx:elif="{{item.tag == 'img'}}">
  776 + <template is="wxParseImg" data="{{item}}" />
  777 + </block>
  778 +
  779 + <!--a类型-->
  780 + <block wx:elif="{{item.tag == 'a'}}">
  781 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  782 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  783 + <template is="wxParse10" data="{{item}}" />
  784 + </block>
  785 + </view>
  786 + </block>
  787 +
  788 + <block wx:elif="{{item.tag == 'br'}}">
  789 + <template is="WxParseBr"></template>
  790 + </block>
  791 + <!--其他块级标签-->
  792 + <block wx:elif="{{item.tagType == 'block'}}">
  793 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  794 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  795 + <template is="wxParse10" data="{{item}}" />
  796 + </block>
  797 + </view>
  798 + </block>
  799 +
  800 + <!--内联标签-->
  801 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  802 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  803 + <template is="wxParse10" data="{{item}}" />
  804 + </block>
  805 + </view>
  806 +
  807 + </block>
  808 +
  809 + <!--判断是否是文本节点-->
  810 + <block wx:elif="{{item.node == 'text'}}">
  811 + <!--如果是,直接进行-->
  812 + <template is="WxEmojiView" data="{{item}}" />
  813 + </block>
  814 +
  815 +</template>
  816 +
  817 +<!--循环模版-->
  818 +<template name="wxParse10">
  819 + <!--<template is="wxParse11" data="{{item}}" />-->
  820 + <!--判断是否是标签节点-->
  821 + <block wx:if="{{item.node == 'element'}}">
  822 + <block wx:if="{{item.tag == 'button'}}">
  823 + <button type="default" size="mini">
  824 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  825 + <template is="wxParse11" data="{{item}}" />
  826 + </block>
  827 + </button>
  828 + </block>
  829 + <!--li类型-->
  830 + <block wx:elif="{{item.tag == 'li'}}">
  831 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  832 + <view class="{{item.classStr}} wxParse-li-inner">
  833 + <view class="{{item.classStr}} wxParse-li-text">
  834 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  835 + </view>
  836 + <view class="{{item.classStr}} wxParse-li-text">
  837 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  838 + <template is="wxParse11" data="{{item}}" />
  839 + </block>
  840 + </view>
  841 + </view>
  842 + </view>
  843 + </block>
  844 +
  845 + <!--video类型-->
  846 + <block wx:elif="{{item.tag == 'video'}}">
  847 + <template is="wxParseVideo" data="{{item}}" />
  848 + </block>
  849 +
  850 + <!--img类型-->
  851 + <block wx:elif="{{item.tag == 'img'}}">
  852 + <template is="wxParseImg" data="{{item}}" />
  853 + </block>
  854 +
  855 + <!--a类型-->
  856 + <block wx:elif="{{item.tag == 'a'}}">
  857 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  858 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  859 + <template is="wxParse11" data="{{item}}" />
  860 + </block>
  861 + </view>
  862 + </block>
  863 +
  864 + <block wx:elif="{{item.tag == 'br'}}">
  865 + <template is="WxParseBr"></template>
  866 + </block>
  867 + <!--其他块级标签-->
  868 + <block wx:elif="{{item.tagType == 'block'}}">
  869 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  870 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  871 + <template is="wxParse11" data="{{item}}" />
  872 + </block>
  873 + </view>
  874 + </block>
  875 +
  876 + <!--内联标签-->
  877 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  878 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  879 + <template is="wxParse11" data="{{item}}" />
  880 + </block>
  881 + </view>
  882 +
  883 + </block>
  884 +
  885 + <!--判断是否是文本节点-->
  886 + <block wx:elif="{{item.node == 'text'}}">
  887 + <!--如果是,直接进行-->
  888 + <template is="WxEmojiView" data="{{item}}" />
  889 + </block>
  890 +
  891 +</template>
  892 +
  893 +<!--循环模版-->
  894 +<template name="wxParse11">
  895 + <!--<template is="wxParse12" data="{{item}}" />-->
  896 + <!--判断是否是标签节点-->
  897 + <block wx:if="{{item.node == 'element'}}">
  898 + <block wx:if="{{item.tag == 'button'}}">
  899 + <button type="default" size="mini">
  900 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  901 + <template is="wxParse12" data="{{item}}" />
  902 + </block>
  903 + </button>
  904 + </block>
  905 + <!--li类型-->
  906 + <block wx:elif="{{item.tag == 'li'}}">
  907 + <view class="{{item.classStr}} wxParse-li" style="{{item.styleStr}}">
  908 + <view class="{{item.classStr}} wxParse-li-inner">
  909 + <view class="{{item.classStr}} wxParse-li-text">
  910 + <view class="{{item.classStr}} wxParse-li-circle"></view>
  911 + </view>
  912 + <view class="{{item.classStr}} wxParse-li-text">
  913 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  914 + <template is="wxParse12" data="{{item}}" />
  915 + </block>
  916 + </view>
  917 + </view>
  918 + </view>
  919 + </block>
  920 +
  921 + <!--video类型-->
  922 + <block wx:elif="{{item.tag == 'video'}}">
  923 + <template is="wxParseVideo" data="{{item}}" />
  924 + </block>
  925 +
  926 + <!--img类型-->
  927 + <block wx:elif="{{item.tag == 'img'}}">
  928 + <template is="wxParseImg" data="{{item}}" />
  929 + </block>
  930 +
  931 + <!--a类型-->
  932 + <block wx:elif="{{item.tag == 'a'}}">
  933 + <view bindtap="wxParseTagATap" class="wxParse-inline {{item.classStr}} wxParse-{{item.tag}}" data-src="{{item.attr.href}}" style="{{item.styleStr}}">
  934 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  935 + <template is="wxParse12" data="{{item}}" />
  936 + </block>
  937 + </view>
  938 + </block>
  939 +
  940 + <block wx:elif="{{item.tag == 'br'}}">
  941 + <template is="WxParseBr"></template>
  942 + </block>
  943 + <!--其他块级标签-->
  944 + <block wx:elif="{{item.tagType == 'block'}}">
  945 + <view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
  946 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  947 + <template is="wxParse12" data="{{item}}" />
  948 + </block>
  949 + </view>
  950 + </block>
  951 +
  952 + <!--内联标签-->
  953 + <view wx:else class="{{item.classStr}} wxParse-{{item.tag}} wxParse-{{item.tagType}}" style="{{item.styleStr}}">
  954 + <block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
  955 + <template is="wxParse12" data="{{item}}" />
  956 + </block>
  957 + </view>
  958 +
  959 + </block>
  960 +
  961 + <!--判断是否是文本节点-->
  962 + <block wx:elif="{{item.node == 'text'}}">
  963 + <!--如果是,直接进行-->
  964 + <template is="WxEmojiView" data="{{item}}" />
  965 + </block>
  966 +
  967 +</template>
  1 +
  2 +/**
  3 + * author: Di (微信小程序开发工程师)
  4 + * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
  5 + * 垂直微信小程序开发交流社区
  6 + *
  7 + * github地址: https://github.com/icindy/wxParse
  8 + *
  9 + * for: 微信小程序富文本解析
  10 + * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
  11 + */
  12 +
  13 +.wxParse{
  14 + margin: 0 5px;
  15 + font-family: Helvetica,sans-serif;
  16 + font-size: 28rpx;
  17 + color: #666;
  18 + line-height: 1.8;
  19 +}
  20 +view{
  21 + word-break:break-all; overflow:auto;
  22 +}
  23 +.wxParse-inline{
  24 + display: inline;
  25 + margin: 0;
  26 + padding: 0;
  27 +}
  28 +/*//标题 */
  29 +.wxParse-div{margin: 0;padding: 0;}
  30 +.wxParse-h1{ font-size:2em; margin: .67em 0 }
  31 +.wxParse-h2{ font-size:1.5em; margin: .75em 0 }
  32 +.wxParse-h3{ font-size:1.17em; margin: .83em 0 }
  33 +.wxParse-h4{ margin: 1.12em 0}
  34 +.wxParse-h5 { font-size:.83em; margin: 1.5em 0 }
  35 +.wxParse-h6{ font-size:.75em; margin: 1.67em 0 }
  36 +
  37 +.wxParse-h1 {
  38 + font-size: 18px;
  39 + font-weight: 400;
  40 + margin-bottom: .9em;
  41 +}
  42 +.wxParse-h2 {
  43 + font-size: 16px;
  44 + font-weight: 400;
  45 + margin-bottom: .34em;
  46 +}
  47 +.wxParse-h3 {
  48 + font-weight: 400;
  49 + font-size: 15px;
  50 + margin-bottom: .34em;
  51 +}
  52 +.wxParse-h4 {
  53 + font-weight: 400;
  54 + font-size: 14px;
  55 + margin-bottom: .24em;
  56 +}
  57 +.wxParse-h5 {
  58 + font-weight: 400;
  59 + font-size: 13px;
  60 + margin-bottom: .14em;
  61 +}
  62 +.wxParse-h6 {
  63 + font-weight: 400;
  64 + font-size: 12px;
  65 + margin-bottom: .04em;
  66 +}
  67 +
  68 +.wxParse-h1, .wxParse-h2, .wxParse-h3, .wxParse-h4, .wxParse-h5, .wxParse-h6, .wxParse-b, .wxParse-strong { font-weight: bolder }
  69 +
  70 +.wxParse-i,.wxParse-cite,.wxParse-em,.wxParse-var,.wxParse-address{font-style:italic}
  71 +.wxParse-pre,.wxParse-tt,.wxParse-code,.wxParse-kbd,.wxParse-samp{font-family:monospace}
  72 +.wxParse-pre{white-space:pre}
  73 +.wxParse-big{font-size:1.17em}
  74 +.wxParse-small,.wxParse-sub,.wxParse-sup{font-size:.83em}
  75 +.wxParse-sub{vertical-align:sub}
  76 +.wxParse-sup{vertical-align:super}
  77 +.wxParse-s,.wxParse-strike,.wxParse-del{text-decoration:line-through}
  78 +/*wxparse-自定义个性化的css样式*/
  79 +/*增加video的css样式*/
  80 +.wxParse-strong,.wxParse-s{display: inline}
  81 +.wxParse-a{
  82 + color: deepskyblue;
  83 + word-break:break-all;
  84 + overflow:auto;
  85 +}
  86 +
  87 +.wxParse-video{
  88 + text-align: center;
  89 + margin: 10px 0;
  90 +}
  91 +
  92 +.wxParse-video-video{
  93 + width:100%;
  94 +}
  95 +
  96 +.wxParse-img{
  97 + /*background-color: #efefef;*/
  98 + overflow: hidden;
  99 +}
  100 +
  101 +.wxParse-blockquote {
  102 + margin: 0;
  103 + padding:10px 0 10px 5px;
  104 + font-family:Courier, Calibri,"宋体";
  105 + background:#f5f5f5;
  106 + border-left: 3px solid #dbdbdb;
  107 +}
  108 +
  109 +.wxParse-code,.wxParse-wxxxcode-style{
  110 + display: inline;
  111 + background:#f5f5f5;
  112 +}
  113 +.wxParse-ul{
  114 + margin: 20rpx 10rpx;
  115 +}
  116 +
  117 +.wxParse-li,.wxParse-li-inner{
  118 + display: flex;
  119 + align-items: baseline;
  120 + margin: 10rpx 0;
  121 +}
  122 +.wxParse-li-text{
  123 +
  124 + align-items: center;
  125 + line-height: 20px;
  126 +}
  127 +
  128 +.wxParse-li-circle{
  129 + display: inline-flex;
  130 + width: 5px;
  131 + height: 5px;
  132 + background-color: #333;
  133 + margin-right: 5px;
  134 +}
  135 +
  136 +.wxParse-li-square{
  137 + display: inline-flex;
  138 + width: 10rpx;
  139 + height: 10rpx;
  140 + background-color: #333;
  141 + margin-right: 5px;
  142 +}
  143 +.wxParse-li-ring{
  144 + display: inline-flex;
  145 + width: 10rpx;
  146 + height: 10rpx;
  147 + border: 2rpx solid #333;
  148 + border-radius: 50%;
  149 + background-color: #fff;
  150 + margin-right: 5px;
  151 +}
  152 +
  153 +/*.wxParse-table{
  154 + width: 100%;
  155 + height: 400px;
  156 +}
  157 +.wxParse-thead,.wxParse-tfoot,.wxParse-tr{
  158 + display: flex;
  159 + flex-direction: row;
  160 +}
  161 +.wxParse-th,.wxParse-td{
  162 + display: flex;
  163 + width: 580px;
  164 + overflow: auto;
  165 +}*/
  166 +
  167 +.wxParse-u {
  168 + text-decoration: underline;
  169 +}
  170 +.wxParse-hide{
  171 + display: none;
  172 +}
  173 +.WxEmojiView{
  174 + align-items: center;
  175 +}
  176 +.wxEmoji{
  177 + width: 16px;
  178 + height:16px;
  179 +}
  180 +.wxParse-tr{
  181 + display: flex;
  182 + border-right:1px solid #e0e0e0;
  183 + border-bottom:1px solid #e0e0e0;
  184 + border-top:1px solid #e0e0e0;
  185 +}
  186 +.wxParse-th,
  187 +.wxParse-td{
  188 + flex:1;
  189 + padding:5px;
  190 + font-size:28rpx;
  191 + border-left:1px solid #e0e0e0;
  192 + word-break: break-all;
  193 +}
  194 +.wxParse-td:last{
  195 + border-top:1px solid #e0e0e0;
  196 +}
  197 +.wxParse-th{
  198 + background:#f0f0f0;
  199 + border-top:1px solid #e0e0e0;
  200 +}
  201 +.wxParse-del{
  202 + display: inline;
  203 +}
  204 +.wxParse-figure {
  205 + overflow: hidden;
  206 +}