diff --git a/LICENSE b/LICENSE
new file mode 100755
index 0000000..77f8df7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 paco
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..3b6c24d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,77 @@
+### 步骤
+
+---
+
+1. 把 `wxParser` 目录放到小程序项目的根目录下
+2. 在需要富文本解析的的 `WXML` 内引入 `wxParser/index.wxml`
+3. 在页面 `JS` 文件内使用 `wxParser.parse(options)` 方法解析 `HTML` 内容
+4. 在小程序项目根目录的 `app.wxss` 内引入 `wxParser` 的默认样式库
+
+---
+
+**`wxParser.parse(options)` 方法的 `options` 参数说明**
+
+| 参数名 | 类型 | 必填 |描述 |
+| :---: | :----: | :----: |:----: |
+| bind | String | 是 | 要绑定的数据名称 |
+| html | String | 是 | HTML 内容 |
+| target | Object | 是 | 绑定数据的模块对象 |
+| enablePreviewImage | Boolean | 否 | 是否启用富文本内的图片预览功能,默认是 |
+| tapLink | Function | 否 | 点击超链接时的回调函数 |
+
+### 示例
+
+---
+
+**WXML**
+
+```
+
+
+
+
+```
+
+**JS**
+
+```
+const wxParser = require('../../wxParser/index');
+
+Page({
+ data: {},
+ onLoad: function () {
+ let that = this;
+ let html = `
hello, wxParser!
`;
+
+ wxParser.parse({
+ bind: 'richText',
+ html: html,
+ target: that,
+ enablePreviewImage: false, // 禁用图片预览功能
+ tapLink: (url) => { // 点击超链接时的回调函数
+ // url 就是 HTML 富文本中 a 标签的 href 属性值
+ // 这里可以自定义点击事件逻辑,比如页面跳转
+ wx.navigateTo({
+ url
+ });
+ }
+ });
+
+ }
+})
+```
+
+**WXSS**
+
+```
+@import '../wxParser/index.wxss'
+```
+
+### 注意
+
+---
+
+- `JS` 中的 `richText` 可以自定义,但是必须要与 `WXML` 中的 `richText` 对应
+- 推荐把 template 放到 ` ` 内部,这样可以受 `wxParser` 控制并具有 `wxParser` 的一些默认样式
+- 不建议直接修改 `wxParser` 的默认样式(因为内容库样式会有定期更新),应该新增一个样式补丁文件用来自定义样式
+
diff --git a/app.js b/app.js
new file mode 100755
index 0000000..a0349ca
--- /dev/null
+++ b/app.js
@@ -0,0 +1,30 @@
+//app.js
+App({
+ onLaunch: function () {
+ //调用API从本地缓存中获取数据
+ var logs = wx.getStorageSync('logs') || []
+ logs.unshift(Date.now())
+ wx.setStorageSync('logs', logs);
+ },
+ getUserInfo: function (cb) {
+ var that = this
+ if (this.globalData.userInfo) {
+ typeof cb == "function" && cb(this.globalData.userInfo)
+ } else {
+ //调用登录接口
+ wx.login({
+ success: function () {
+ wx.getUserInfo({
+ success: function (res) {
+ that.globalData.userInfo = res.userInfo
+ typeof cb == "function" && cb(that.globalData.userInfo)
+ }
+ })
+ }
+ })
+ }
+ },
+ globalData: {
+ userInfo: null
+ }
+})
diff --git a/app.json b/app.json
new file mode 100755
index 0000000..aca9fab
--- /dev/null
+++ b/app.json
@@ -0,0 +1,12 @@
+{
+ "pages": [
+ "pages/index/index",
+ "pages/logs/logs"
+ ],
+ "window": {
+ "backgroundTextStyle": "light",
+ "navigationBarBackgroundColor": "#fff",
+ "navigationBarTitleText": "WeChat",
+ "navigationBarTextStyle": "black"
+ }
+}
diff --git a/app.wxss b/app.wxss
new file mode 100755
index 0000000..485171d
--- /dev/null
+++ b/app.wxss
@@ -0,0 +1,15 @@
+/* 引入内容渲染模块样式 */
+
+@import "/wxParser/index.wxss";
+
+/**app.wxss**/
+
+.container {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ padding: 200rpx 0;
+ box-sizing: border-box;
+}
diff --git a/pages/index/index.js b/pages/index/index.js
new file mode 100755
index 0000000..8eaef7b
--- /dev/null
+++ b/pages/index/index.js
@@ -0,0 +1,23 @@
+// 获取应用实例
+let app = getApp();
+
+let wxParser = require('../../wxParser/index');
+
+Page({
+ data: {},
+ onLoad: function () {
+ let that = this;
+ let html = `文本内容 p 直属内容test
分类
介 绍 信 息 哈 哈 哈
`;
+ wxParser.parse({
+ bind: 'richText',
+ html: html,
+ target: that,
+ enablePreviewImage: false,
+ tapLink: (url) => {
+ wx.navigateTo({
+ url
+ })
+ }
+ });
+ }
+})
diff --git a/pages/index/index.wxml b/pages/index/index.wxml
new file mode 100755
index 0000000..74916a7
--- /dev/null
+++ b/pages/index/index.wxml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/pages/index/index.wxss b/pages/index/index.wxss
new file mode 100755
index 0000000..92d0c89
--- /dev/null
+++ b/pages/index/index.wxss
@@ -0,0 +1,26 @@
+/**index.wxss**/
+
+.userinfo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.userinfo-avatar {
+ width: 128rpx;
+ height: 128rpx;
+ margin: 20rpx;
+ border-radius: 50%;
+}
+
+.userinfo-nickname {
+ color: #aaa;
+}
+
+.usermotto {
+ margin-top: 200px;
+}
+
+.hehe {
+ color: #f00;
+}
diff --git a/utils/util.js b/utils/util.js
new file mode 100755
index 0000000..c5cccf3
--- /dev/null
+++ b/utils/util.js
@@ -0,0 +1,21 @@
+function formatTime(date) {
+ var year = date.getFullYear()
+ var month = date.getMonth() + 1
+ var day = date.getDate()
+
+ var hour = date.getHours()
+ var minute = date.getMinutes()
+ var second = date.getSeconds()
+
+
+ return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
+}
+
+function formatNumber(n) {
+ n = n.toString()
+ return n[1] ? n : '0' + n
+}
+
+module.exports = {
+ formatTime: formatTime
+}
diff --git a/wxParser/codeTransformation.js b/wxParser/codeTransformation.js
new file mode 100755
index 0000000..607debe
--- /dev/null
+++ b/wxParser/codeTransformation.js
@@ -0,0 +1,174 @@
+/**
+ * 特殊字符映射表
+ * @type {Object}
+ */
+const codeMap = {
+ // HTML 支持的数学符号
+ '∀': '∀',
+ '∂': '∂',
+ '&exists;': '∃',
+ '∅': '∅',
+ '∇': '∇',
+ '∈': '∈',
+ '∉': '∉',
+ '∋': '∋',
+ '∏': '∏',
+ '∑': '∑',
+ '−': '−',
+ '∗': '∗',
+ '√': '√',
+ '∝': '∝',
+ '∞': '∞',
+ '∠': '∠',
+ '∧': '∧',
+ '∨': '∨',
+ '∩': '∩',
+ '∩': '∪',
+ '∫': '∫',
+ '∴': '∴',
+ '∼': '∼',
+ '≅': '≅',
+ '≈': '≈',
+ '≠': '≠',
+ '≤': '≤',
+ '≥': '≥',
+ '⊂': '⊂',
+ '⊃': '⊃',
+ '⊄': '⊄',
+ '⊆': '⊆',
+ '⊇': '⊇',
+ '⊕': '⊕',
+ '⊗': '⊗',
+ '⊥': '⊥',
+ '⋅': '⋅',
+
+ // HTML 支持的希腊字母
+ 'Α': 'Α',
+ 'Β': 'Β',
+ 'Γ': 'Γ',
+ 'Δ': 'Δ',
+ 'Ε': 'Ε',
+ 'Ζ': 'Ζ',
+ 'Η': 'Η',
+ 'Θ': 'Θ',
+ 'Ι': 'Ι',
+ 'Κ': 'Κ',
+ 'Λ': 'Λ',
+ 'Μ': 'Μ',
+ 'Ν': 'Ν',
+ 'Ξ': 'Ν',
+ 'Ο': 'Ο',
+ 'Π': 'Π',
+ 'Ρ': 'Ρ',
+ 'Σ': 'Σ',
+ 'Τ': 'Τ',
+ 'Υ': 'Υ',
+ 'Φ': 'Φ',
+ 'Χ': 'Χ',
+ 'Ψ': 'Ψ',
+ 'Ω': 'Ω',
+ 'α': 'α',
+ 'β': 'β',
+ 'γ': 'γ',
+ 'δ': 'δ',
+ 'ε': 'ε',
+ 'ζ': 'ζ',
+ 'η': 'η',
+ 'θ': 'θ',
+ 'ι': 'ι',
+ 'κ': 'κ',
+ 'λ': 'λ',
+ 'μ': 'μ',
+ 'ν': 'ν',
+ 'ξ': 'ξ',
+ 'ο': 'ο',
+ 'π': 'π',
+ 'ρ': 'ρ',
+ 'ς': 'ς',
+ 'σ': 'σ',
+ 'τ': 'τ',
+ 'υ': 'υ',
+ 'φ': 'φ',
+ 'χ': 'χ',
+ 'ψ': 'ψ',
+ 'ω': 'ω',
+ 'ϑ': 'ϑ',
+ 'ϒ': 'ϒ',
+ 'ϖ': 'ϖ',
+ '·': '·',
+
+ // 常用解析
+ ' ': ' ',
+ '"': "'",
+ '&': '&',
+ '<': '<',
+ '>': '>',
+
+ // HTML 支持的其他实体
+ 'Œ': 'Œ',
+ 'œ': 'œ',
+ 'Š': 'Š',
+ 'š': 'š',
+ 'Ÿ': 'Ÿ',
+ 'ƒ': 'ƒ',
+ 'ˆ': 'ˆ',
+ '˜': '˜',
+ ' ': '',
+ ' ': '',
+ ' ': '',
+ '': '',
+ '': '',
+ '': '',
+ '': '',
+ '–': '–',
+ '—': '—',
+ '‘': '‘',
+ '’': '’',
+ '‚': '‚',
+ '“': '“',
+ '”': '”',
+ '„': '„',
+ '†': '†',
+ '‡': '‡',
+ '•': '•',
+ '…': '…',
+ '‰': '‰',
+ '′': '′',
+ '″': '″',
+ '‹': '‹',
+ '›': '›',
+ '‾': '‾',
+ '€': '€',
+ '™': '™',
+ '←': '←',
+ '↑': '↑',
+ '→': '→',
+ '↓': '↓',
+ '↔': '↔',
+ '↵': '↵',
+ '⌈': '⌈',
+ '⌉': '⌉',
+ '⌊': '⌊',
+ '⌋': '⌋',
+ '◊': '◊',
+ '♠': '♠',
+ '♣': '♣',
+ '♥': '♥',
+ '♦': '♦',
+};
+
+/**
+ * 转换特殊字符
+ * @param {String} str 待转换字符串
+ * @return {String} 转换后的字符串
+ */
+const transform = (str) => {
+ for (let code in codeMap) {
+ str = str.replace(new RegExp(code, 'g'), codeMap[code]);
+ }
+ return str;
+};
+
+module.exports = {
+ transform
+}
diff --git a/wxParser/elements.js b/wxParser/elements.js
new file mode 100755
index 0000000..cdb3d7b
--- /dev/null
+++ b/wxParser/elements.js
@@ -0,0 +1,11 @@
+const utils = require('./utils');
+const makeMap = utils.makeMap;
+
+module.exports = {
+ empty: makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'),
+ 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'),
+ 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'),
+ closeSelf: makeMap('br,hr'),
+ fillAttrs: makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
+ special: makeMap('script,style')
+};
diff --git a/wxParser/html2json.js b/wxParser/html2json.js
new file mode 100755
index 0000000..184ae90
--- /dev/null
+++ b/wxParser/html2json.js
@@ -0,0 +1,170 @@
+const utils = require('./utils');
+const elements = require('./elements');
+const codeTransformation = require('./codeTransformation');
+const htmlParser = require('./htmlparser');
+
+/**
+ * 移除文档头信息
+ * @param {String} str HTML 内容
+ * @return {String}
+ */
+const removeDOCTYPE = (str) => {
+ return str.replace(/<\?xml.*\?>\n/, '').replace(/<.*!doctype.*\>\n/, '').replace(/<.*!DOCTYPE.*\>\n/, '');
+};
+
+/**
+ * HTML 内容转化为 JSON 格式的对象
+ * @param {String} html HTML 内容
+ * @param {String} bindName 绑定的数据名
+ * @return {Object}
+ */
+const html2json = (html, bindName) => {
+ html = removeDOCTYPE(html);
+ html = codeTransformation.transform(html);
+
+ // 节点缓冲区,与 htmlparser.js 中的 stack 对应,只存储非自闭和标签
+ // 比如 ,而非 等
+ let bufferNodes = [];
+ let nodeStyles = [];
+ // html2json 结果
+ let results = {
+ nodes: [],
+ images: [],
+ imageUrls: []
+ };
+
+ /**
+ * 把节点放到父节点的 nodes 列表
+ * @param {Object} node 节点对象
+ */
+ const putNode2ParentNodeList = (node) => {
+ if (bufferNodes.length === 0) { // 表明关闭此 node 时,不存在任何未关闭标签,也就是不存在父元素,所以直接挂到根节点即可
+ results.nodes.push(node);
+ } else {
+ // 如果节点缓冲区还有节点,子节点会不断的被放到该子节点的父节点下,形成一个嵌套引用的节点对象。
+ // 直到缓冲区没有节点,此时组装起来的整个嵌套引用节点对象会被放到根节点的 results.nodes 下
+ let parent = bufferNodes[0]; // 取该 node 的父级节点
+ if (parent.nodes === undefined) {
+ parent.nodes = [];
+ }
+ parent.nodes.push(node);
+ }
+ };
+
+ // 开始解析 HTML
+ // 核心思路:
+ // 1、遇到开始标签时,如果该标签是非自闭合标签,就把该节点存到缓存区(入栈),
+ // 如果是自闭合标签、空标签等就直接存到根节点下(因为这种几点没有子节点)。
+ // 2、当遇到文本时(文本也是节点),判断缓冲区是否还有节点,如果有,证明该节点有父节点,
+ // 需要把此节点放到父节点的 nodes 列表,如果没有,则证明该节点没有父节点了,放到根节点即可。
+ // 3、当遇到结束标签时,就从缓存区取出第一个节点(出栈),比较是否与该结束标签对应,
+ // 如果不对应,证明逻辑出错。如果对应,则判断缓冲区是否还有节点,如果有,证明该节点有父节点,
+ // 需要把此节点放到父节点的 nodes 列表,如果没有,则证明该节点没有父节点了,放到根节点即可。
+ //
+ // 总体来说,就是一个进栈出栈(节点缓冲区)的算法问题。
+ htmlParser.parseHtml(html, {
+ /**
+ * 处理开始标签
+ * @param {String} tag 标签名称
+ * @param {Array} attrs 属性
+ * @param {Boolean} isUnary 是否是自闭合标签
+ */
+ start: function (tag, attrs, isUnary) {
+ let node = {
+ node: 'element',
+ tag: tag
+ };
+
+ if (elements.block[tag]) {
+ node.tagType = 'block';
+ } else if (elements.inline[tag]) {
+ node.tagType = 'inline';
+ } else if (elements.closeSelf[tag]) {
+ node.tagType = 'closeSelf';
+ }
+
+ nodeStyles = [];
+
+ if (attrs.length) {
+ node.attr = {};
+ attrs.map((item) => {
+ if (item.name === 'style') { // 对 style 做单独处理,因为后面会根据 tag 添加更多的 style
+ if (nodeStyles.indexOf(item.value) === -1) {
+ nodeStyles.push(item.value);
+ }
+ }
+ if (item.name === 'color') {
+ nodeStyles.push('color: ' + item.value);
+ }
+ if (node.tag === 'font' && item.name === 'size') {
+ nodeStyles.push('font-size: ' + utils.getFontSizeByAttribsSize(item.value));
+ }
+
+ // 特殊属性做转换
+ if (item.name === 'class') {
+ node.classStr = item.value;
+ }
+
+ node.attr[item.name] = item.value; // 重复的属性,后面的会覆盖前面的
+ });
+
+ node.styleStr = nodeStyles.join(' ');
+ }
+
+ // img 标签 添加额外数据
+ if (node.tag === 'img') {
+ node.imgIndex = results.images.length;
+ node.from = bindName;
+
+ results.images.push(node);
+ results.imageUrls.push(node.attr.src);
+ }
+
+ if (isUnary) {
+ // 自闭合标签,比如
+ // 这种类型不会进入 end 函数或者 text 函数处理,在 start 函数放入到父元素的 nodes 列表即可
+ putNode2ParentNodeList(node);
+ } else {
+ // 只要有非自闭&标签就往缓冲区保存节点,等待关闭
+ bufferNodes.unshift(node);
+ }
+ },
+ /**
+ * 处理关闭标签
+ * @param {String} tag 标签名称
+ */
+ end: function (tag) {
+ let node = bufferNodes.shift(); // 取出缓冲区的第一个的未关闭标签,也就是与该结束标签对应的标签
+
+ if (node.tag !== tag) {
+ throw new Error('不匹配的关闭标签');
+ }
+
+ putNode2ParentNodeList(node);
+ },
+ /**
+ * 处理文本内容
+ * @param {String} text 文本字符串
+ */
+ text: function (text) {
+ let node = {
+ node: 'text',
+ text: text,
+ };
+
+ putNode2ParentNodeList(node);
+ },
+ /**
+ * 处理评论内容
+ * @param {String} content 注释内容
+ */
+ comment: function (content) {},
+ });
+
+ return results;
+
+};
+
+module.exports = {
+ html2json
+};
diff --git a/wxParser/htmlparser.js b/wxParser/htmlparser.js
new file mode 100755
index 0000000..5a3c78c
--- /dev/null
+++ b/wxParser/htmlparser.js
@@ -0,0 +1,162 @@
+const elements = require('./elements');
+
+const startTagReg = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+const endTagReg = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+const attrReg = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+/**
+ * 解析 HTML
+ * @param {String} html HTML 内容
+ * @param {Object} handler 处理器
+ */
+const parseHtml = (html, handler) => {
+ let index;
+ let isText;
+ let match;
+ let stack = [];
+ let last = html;
+ // 只存放非自闭合普通标签,不存放空标签、特殊标签和自闭合标签
+ stack.last = function () {
+ return this[this.length - 1];
+ };
+
+ while (html) {
+ isText = true;
+
+ if (!stack.length || !elements.special[stack.last()]) {
+ if (html.indexOf(''); // indexOf 会匹配到第一个满足条件的字符位置
+ if (index !== -1) {
+ if (handler.comment) {
+ // 因为注释信息不会像其他标签那样包含内部标签,所以可以直接传注释内容给 handler 处理
+ handler.comment(html.substring(4, index));
+ }
+ html = html.substring(index + 3);
+ isText = false;
+ }
+ } else if (html.indexOf('') === 0) { // end tag
+ match = html.match(endTagReg);
+ if (match) {
+ html = html.substring(match[0].length);
+ match[0].replace(endTagReg, parseEndTag);
+ isText = false;
+ }
+ } else if (html.indexOf('<') === 0) { // start tag
+ match = html.match(startTagReg);
+ if (match) {
+ html = html.substring(match[0].length);
+ match[0].replace(startTagReg, parseStartTag);
+ isText = false;
+ }
+ }
+
+ // 处理文本内容
+ if (isText) {
+ index = html.indexOf('<');
+ let text = ''
+ while (index === 0 && !html.match(startTagReg)) { // 处理以 < 开头,但是却不满足 startTagReg 的情况
+ text += '<';
+ html = html.substring(1);
+ index = html.indexOf('<');
+ }
+ text += index < 0 ? html : html.substring(0, index);
+ html = index < 0 ? '' : html.substring(index);
+
+ if (handler.text) {
+ handler.text(text);
+ }
+ }
+
+ } else {
+ html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) {
+ // 丢弃 special tag 内部的所有内容,包括该 special tag 的闭合标签
+ return '';
+ });
+ }
+
+ if (html === last) {
+ throw new Error('解析 html 内容时出席异常');
+ }
+
+ last = html;
+ }
+
+ if (stack.length) {
+ throw new Error('HTML 内容存在未关闭的标签,会导致未关闭标签的内容无法被解析');
+ }
+
+ /**
+ * 解析开始标签
+ * @param {String} match 匹配结果
+ * @param {String} tagName 标签名称
+ * @param {String} attrsStr 属性信息
+ * @param {String} unary
+ */
+ function parseStartTag(match, tagName, attrsStr, unary) {
+ // 举例:
+ // match:
+ // tagName: p
+ // attrsStr: style="text-align: center; " width="100"
+
+ tagName = tagName.toLowerCase();
+
+ let isUnary = elements.empty[tagName] || unary;
+
+ // 空标签、自闭和标签、特殊标签不需要进 stack
+ if (!isUnary && !elements.closeSelf[tagName] && !elements.special[tagName]) {
+ stack.push(tagName);
+ }
+
+ if (handler.start && !elements.special[tagName]) {
+ let attrs = [];
+
+ attrsStr.replace(attrReg, function (match, name, value) {
+
+ if (elements.fillAttrs[name]) {
+ value = name;
+ }
+
+ attrs.push({
+ name: name,
+ value: value || '',
+ });
+ });
+
+ handler.start(tagName, attrs, isUnary);
+
+ }
+ }
+
+ /**
+ * 解析结束标签
+ * @param {String} match 匹配结果
+ * @param {String} tagName 标签名称
+ */
+ function parseEndTag(match, tagName) {
+ if (!tagName) {
+ return;
+ }
+
+ // 找到最近同种类型的未关闭标签的位置
+ tagName = tagName.toLowerCase();
+ let closestOpenedTagPos = -1;
+ for (let pos = stack.length - 1; pos >= 0; pos--) {
+ if (stack[pos] == tagName) {
+ closestOpenedTagPos = pos;
+ break;
+ }
+ }
+
+ if (closestOpenedTagPos >= 0) {
+ if (handler.end) {
+ handler.end(stack[closestOpenedTagPos]);
+ }
+ // 处理后从 stack 中移除该标签
+ stack.length = closestOpenedTagPos;
+ }
+ }
+};
+
+module.exports = {
+ parseHtml
+};
diff --git a/wxParser/index.js b/wxParser/index.js
new file mode 100755
index 0000000..92b4320
--- /dev/null
+++ b/wxParser/index.js
@@ -0,0 +1,57 @@
+const html2Json = require('./html2json');
+
+/**
+ * 主解析函数
+ * @param {Object} options 配置参数
+ * @param {String} [options.bind=wxParserData] 绑定的变量名
+ * @param {String} options.html HTML 内容
+ * @param {Object} options.target 要绑定的模块对象
+ * @param {Boolean} [options.enablePreviewImage=true] 是否启用预览图片功能
+ * @param {Function} [options.tapLink] 点击超链接后的回调函数
+ */
+const parse = ({ bind = 'wxParserData', html, target, enablePreviewImage = true, tapLink }) => {
+ if (Object.prototype.toString.call(html) !== '[object String]') {
+ throw new Error('HTML 内容必须是字符串');
+ }
+ let that = target;
+ let transData = {}; // 存放转化后的数据
+ transData = html2Json.html2json(html, bind);
+
+ let bindData = {};
+ bindData[bind] = transData;
+
+ that.setData(bindData)
+
+ // 加载图片后回调函数
+ that.loadedWxParserImg = (e) => {
+
+ };
+
+ // 点击图片
+ that.tapWxParserImg = (e) => {
+ if (!enablePreviewImage) {
+ return;
+ }
+ let src = e.target.dataset.src;
+ let tagFrom = e.target.dataset.from;
+ if (typeof (tagFrom) !== 'undefined' && tagFrom.length > 0) {
+ wx.previewImage({
+ current: src, // 当前显示图片的 http 链接
+ urls: that.data[tagFrom].imageUrls // 需要预览的图片 http 链接列表
+ })
+ }
+ };
+
+ // 点击超链接
+ if (Object.prototype.toString.call(tapLink) === '[object Function]') {
+ that.tapWxParserA = (e) => {
+ let href = e.currentTarget.dataset.href;
+ tapLink(href);
+ };
+ }
+
+};
+
+module.exports = {
+ parse
+}
diff --git a/wxParser/index.wxml b/wxParser/index.wxml
new file mode 100755
index 0000000..61542b4
--- /dev/null
+++ b/wxParser/index.wxml
@@ -0,0 +1,899 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.text}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wxParser/index.wxss b/wxParser/index.wxss
new file mode 100755
index 0000000..0141154
--- /dev/null
+++ b/wxParser/index.wxss
@@ -0,0 +1,229 @@
+/**
+ * wxParser 基础样式
+ */
+
+.wxParser-div,
+.wxParser-p,
+{
+ word-break: break-all;
+ overflow: auto;
+ max-width: 100%;
+}
+
+.wxParser-inline {
+ display: inline;
+ margin: 0;
+ padding: 0;
+}
+
+.wxParser-div {
+ margin: 0;
+ padding: 0;
+}
+
+.wxParser-p {
+ margin: 5rpx 0;
+}
+
+.wxParser-br {
+ height: 1em;
+}
+
+.wxParser-h1 {
+ font-size: 2em;
+ margin: .67em 0;
+}
+
+.wxParser-h2 {
+ font-size: 1.5em;
+ margin: .75em 0;
+}
+
+.wxParser-h3 {
+ font-size: 1.17em;
+ margin: .83em 0;
+}
+
+.wxParser-h4 {
+ margin: 1.12em 0;
+}
+
+.wxParser-h5 {
+ font-size: .83em;
+ margin: 1.5em 0;
+}
+
+.wxParser-h6 {
+ font-size: .75em;
+ margin: 1.67em 0;
+}
+
+.wxParser-h1,
+.wxParser-h2,
+.wxParser-h3,
+.wxParser-h4,
+.wxParser-h5,
+.wxParser-h6,
+.wxParser-b,
+.wxParser-strong {
+ font-weight: bolder;
+}
+
+.wxParser-i,
+.wxParser-cite,
+.wxParser-em,
+.wxParser-var,
+.wxParser-address {
+ font-style: italic;
+}
+
+.wxParser-pre,
+.wxParser-tt,
+.wxParser-code,
+.wxParser-kbd,
+.wxParser-samp {
+ font-family: monospace;
+}
+
+.wxParser-pre {
+ white-space: pre;
+}
+
+.wxParser-big {
+ font-size: 1.17em
+}
+
+.wxParser-small,
+.wxParser-sub,
+.wxParser-sup {
+ font-size: .83em
+}
+
+.wxParser-sub {
+ vertical-align: sub;
+}
+
+.wxParser-sup {
+ vertical-align: super;
+}
+
+.wxParser-s,
+.wxParser-strike,
+.wxParser-del {
+ text-decoration: line-through;
+}
+
+.wxParser-strong,
+.wxParser-s {
+ display: inline;
+}
+
+.wxParser-u {
+ text-decoration: underline;
+}
+
+.wxParser-u {
+ text-decoration: underline;
+}
+
+.wxParser-a {
+ color: deepskyblue;
+ word-break: break-all;
+ overflow: auto;
+}
+
+.wxParser-video {
+ text-align: center;
+ margin: 10rpx 0;
+}
+
+.wxParser-video-video {
+ width: 100%;
+}
+
+.wxParser-img {
+ overflow: hidden;
+ max-width: 100%;
+}
+
+.wxParser-blockquote {
+ margin: 0;
+ padding: 10rpx 0 10rpx 5rpx;
+ font-family: Courier, Calibri, "宋体";
+ background: #f5f5f5;
+ border-left: 3rpx solid #dbdbdb;
+}
+
+.wxParser-ul {
+ margin: 20rpx 10rpx;
+}
+
+.wxParser-li {
+ margin: 10rpx 0;
+}
+
+.wxParser-li,
+.wxParser-li-inner {
+ display: flex;
+ align-items: baseline;
+}
+
+.wxParser-li-text {
+ align-items: center;
+ line-height: 1em;
+}
+
+.wxParser-li-circle {
+ display: inline-flex;
+ width: 10rpx;
+ height: 10rpx;
+ background-color: #333;
+ margin-right: 12rpx;
+ border-radius: 50%;
+}
+
+.wxParser-li-square {
+ display: inline-flex;
+ width: 10rpx;
+ height: 10rpx;
+ background-color: #333;
+ margin-right: 5rpx;
+}
+
+.wxParser-li-ring {
+ display: inline-flex;
+ width: 10rpx;
+ height: 10rpx;
+ border: 2rpx solid #333;
+ border-radius: 50%;
+ background-color: #fff;
+ margin-right: 5rpx;
+}
+
+.wxParser-hidden {
+ display: none;
+}
+
+.wxParser-tr {
+ display: flex;
+ border-right: 1rpx solid #e0e0e0;
+ border-bottom: 1rpx solid #e0e0e0;
+}
+
+.wxParser-th,
+.wxParser-td {
+ flex: 1;
+ padding: 5rpx;
+ font-size: 28rpx;
+ border-left: 1rpx solid #e0e0e0;
+ word-break: break-all;
+}
+
+.wxParser-td:last {
+ border-top: 1rpx solid #e0e0e0;
+}
+
+.wxParser-th {
+ background: #f0f0f0;
+ border-top: 1rpx solid #e0e0e0;
+}
diff --git a/wxParser/utils.js b/wxParser/utils.js
new file mode 100755
index 0000000..dd10867
--- /dev/null
+++ b/wxParser/utils.js
@@ -0,0 +1,47 @@
+module.exports = {
+ /**
+ * 生成 Map
+ * @param {String} str 以逗号分隔的字符串
+ * @return {Object} 映射表
+ */
+ makeMap: (str) => {
+ let map = {};
+ let items = str.split(',');
+ for (let i = 0, len = items.length; i < len; i++) {
+ map[items[i]] = true;
+ }
+ return map;
+ },
+ /**
+ * 根据 size 属性得到字体大小
+ * @param {Number|String} size
+ * @return {String}
+ */
+ getFontSizeByAttribsSize: function (size) {
+ var fontSize;
+ size = parseInt(size, 10);
+ switch (size) {
+ case 2:
+ fontSize = 0.75;
+ break;
+ case 3:
+ fontSize = 1;
+ break;
+ case 4:
+ fontSize = 1.17;
+ break;
+ case 5:
+ fontSize = 1.5;
+ break;
+ case 6:
+ fontSize = 2;
+ break;
+ case 7:
+ fontSize = 3;
+ break;
+ default:
+ fontSize = 1;
+ }
+ return fontSize + 'em';
+ },
+}