Authored by 肖亚东

文章详情富文本解析更换 使用parser

{
"usingComponents": {
"Parser":"../../vendors/Parser/index"
}
}
... ...
... ... @@ -10,33 +10,34 @@
</view>
<!-- 文章内容 -->
<view class="newsDetail-content">
<block wx:for="{{replyTemArray.nodes}}" wx:key="detailKey" wx:for-index="findex" wx:for-item="detail">
<block wx:if="{{isActivtyDetail}}">
<view class="item-block text-block">
<template is="wxParse" data="{{wxParseData:detail.nodes}}"/>
</view>
</block>
<block wx:else>
<view class="item-block text-block" wx:if="{{detail.tag=='p'}}">
<template is="wxParse" data="{{wxParseData:detail.nodes}}"/>
</view>
</block>
<view class="item-block small-img-block" wx:if="{{detail.smallImage.length}}">
<image src="{{sitem.src}}" wx:for="{{detail.smallImage}}" wx:key="skey" wx:for-item="sitem"></image>
</view>
<view class="item-block video-block" wx:if="{{detail.tag=='object'}}">
<video src="{{detail.video.src}}"
controls="{{true}}"
custom-cache="{{false}}"
show-play-btn="{{true}}"
poster="{{detail.video.cover_image}}"
preload="none">不好意思,您的浏览器不支持此视频!</video>
</view>
<Parser html="{{articleInfo.content0}}" />
<!-- <block wx:if="{{isActivtyDetail}}">
<Parser html="{{articleInfo.content}}" />
</block>
<block wx:else>
<block wx:for="{{replyTemArray.nodes}}" wx:key="detailKey" wx:for-index="findex" wx:for-item="detail">
<view class="item-block text-block" wx:if="{{detail.tag=='p'}}">
<template is="wxParse" data="{{wxParseData:detail.nodes}}"/>
</view>
<view class="item-block small-img-block" wx:if="{{detail.smallImage.length}}">
<image src="{{sitem.src}}" wx:for="{{detail.smallImage}}" wx:key="skey" wx:for-item="sitem"></image>
</view>
<view class="item-block video-block" wx:if="{{detail.tag=='object'}}">
<video src="{{detail.video.src}}"
controls="{{true}}"
custom-cache="{{false}}"
show-play-btn="{{true}}"
poster="{{detail.video.cover_image}}"
preload="none">不好意思,您的浏览器不支持此视频!</video>
</view>
</block>
</block> -->
</view>
</view>
... ...
//CssTokenizer.js
function CssTokenizer(style = '', tagStyle = {}) {
this.res = tagStyle;
this._state = "SPACE";
this._buffer = style;
this._sectionStart = 0;
this._index = 0;
this._name = '';
this._content = '';
this._list = [];
this._comma = false;
}
CssTokenizer.prototype.SPACE = function(c) {
if (/[a-zA-Z.#]/.test(c)) {
this._sectionStart = this._index;
this._state = "InName";
} else if (c == '@') this._state = "Ignore1";
else if (c == '/') this._state = "BeforeComment";
};
CssTokenizer.prototype.BeforeComment = function(c) {
if (c == '*') this._state = "InComment";
else {
this._index--;
this._state = "SPACE";
}
};
CssTokenizer.prototype.InComment = function(c) {
if (c == '*') this._state = "AfterComment";
};
CssTokenizer.prototype.AfterComment = function(c) {
if (c == '/') this._state = "SPACE";
else {
this._index--;
this._state = "InComment"
}
};
CssTokenizer.prototype.InName = function(c) {
if (c == '{') {
this._list.push(this._buffer.substring(this._sectionStart, this._index))
this._sectionStart = this._index + 1;
this._state = "InContent";
} else if (c == ',') {
this._list.push(this._buffer.substring(this._sectionStart, this._index));
this._sectionStart = this._index + 1;
this._comma = true;
} else if ((c == '.' || c == '#') && !this._comma) {
this._buffer = this._buffer.splice(this._index, 1, ' ');
} else if (/\s/.test(c)) {
this._name = this._buffer.substring(this._sectionStart, this._index);
this._state = "NameSpace";
} else if (/[>:\[]/.test(c)) {
if (this._list.length) this._state = "IgnoreName";
else this._state = "Ignore1";
} else this._comma = false;
};
CssTokenizer.prototype.NameSpace = function(c) {
if (c == '{') {
this._list.push(this._name);
this._sectionStart = this._index + 1;
this._state = "InContent";
} else if (c == ',') {
this._comma = true;
this._list.push(this._name);
this._sectionStart = this._index + 1;
this._state = "InName"
} else if (/\S/.test(c)) {
if (this._comma) {
this._sectionStart = this._index;
this._index--;
this._state = "InName";
} else if (this._list.length) this._state = "IgnoreName";
else this._state = "Ignore1"
}
};
CssTokenizer.prototype.InContent = function(c) {
if (c == '}') {
this._content = this._buffer.substring(this._sectionStart, this._index);
for (let item of this._list)
this.res[item] = (this.res[item] || '') + this._content;
this._list = [];
this._comma = false;
this._state = "SPACE";
}
};
CssTokenizer.prototype.IgnoreName = function(c) {
if (c == ',') {
this._sectionStart = this._index + 1;
this._state = "InName";
} else if (c == '{') {
this._sectionStart = this._index + 1;
this._state = "InContent";
}
}
CssTokenizer.prototype.Ignore1 = function(c) {
if (c == ';') {
this._state = "SPACE";
this._sectionStart = this._index + 1;
} else if (c == '{') this._state = "Ignore2";
};
CssTokenizer.prototype.Ignore2 = function(c) {
if (c == '}') {
this._state = "SPACE";
this._sectionStart = this._index + 1;
} else if (c == '{') this._state = "Ignore3";
};
CssTokenizer.prototype.Ignore3 = function(c) {
if (c == '}') this._state = "Ignore2";
};
CssTokenizer.prototype.parse = function() {
for (; this._index < this._buffer.length; this._index++)
this[this._state](this._buffer[this._index]);
return this.res;
};
module.exports = CssTokenizer;
\ No newline at end of file
... ...
//DomHandler.js
const CssTokenizer = require('./CssTokenizer.js');
const CanIUse = require('./api.js').versionHigherThan('2.7.1');
const Common = 1,
Rich = 2;
const trustTag = {
a: Rich,
abbr: Common,
audio: Rich,
b: Common,
blockquote: Common,
br: Rich,
code: Common,
col: Rich,
colgroup: Rich,
dd: Common,
del: Common,
dl: Common,
dt: Common,
div: Common,
em: Common,
fieldset: Rich,
font: Common,
h1: Rich,
h2: Rich,
h3: Rich,
h4: Rich,
h5: Rich,
h6: Rich,
hr: Rich,
i: Common,
img: Common,
ins: Common,
label: Common,
legend: Rich,
li: Rich,
ol: Rich,
p: Common,
q: Common,
source: Rich,
span: Common,
strong: Common,
sub: Rich,
sup: Rich,
table: Rich,
tbody: Rich,
td: Rich,
tfoot: Rich,
th: Rich,
thead: Rich,
tr: Rich,
u: Common,
ul: Rich,
video: Common
};
const blockTag = {
address: true,
article: true,
aside: true,
body: true,
center: true,
cite: true,
footer: true,
header: true,
html: true,
nav: true,
pre: true,
section: true
}
const textTag = {
a: true,
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
font: true,
i: true,
ins: true,
label: true,
mark: true,
q: true,
s: true,
small: true,
span: true,
strong: true,
u: true
};
const ignoreTag = {
area: true,
base: true,
basefont: true,
canvas: true,
circle: true,
command: true,
ellipse: true,
embed: true,
frame: true,
head: true,
iframe: true,
input: true,
isindex: true,
keygen: true,
line: true,
link: true,
map: true,
meta: true,
param: true,
path: true,
polygon: true,
polyline: true,
rect: true,
script: true,
stop: true,
textarea: true,
title: true,
track: true,
use: true,
wbr: true
};
if (CanIUse) {
trustTag.bdi = Rich;
trustTag.bdo = Rich;
trustTag.caption = Rich;
trustTag.rt = Rich;
trustTag.ruby = Rich;
ignoreTag.rp = true;
trustTag.big = Common;
trustTag.small = Common;
trustTag.pre = Rich;
delete blockTag.pre;
}
//添加默认值
function initStyle(tagStyle) {
tagStyle.a = "display:inline;color:#366092;word-break:break-all;" + (tagStyle.a || "");
tagStyle.address = "font-style:italic;" + (tagStyle.address || "");
tagStyle.blockquote = tagStyle.blockquote || 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px';
tagStyle.center = 'text-align:center;' + (tagStyle.center || "");
tagStyle.cite = "font-style:italic;" + (tagStyle.cite || "");
tagStyle.code = tagStyle.code || 'padding:0 1px 0 1px;margin-left:2px;margin-right:2px;background-color:#f8f8f8;border:1px solid #cccccc;border-radius:3px';
tagStyle.dd = "margin-left:40px;" + (tagStyle.dd || "");
tagStyle.img = "max-width:100%;" + (tagStyle.img || "");
tagStyle.mark = "display:inline;background-color:yellow;" + (tagStyle.mark || "");
tagStyle.pre = "overflow:scroll;" + (tagStyle.pre || 'background-color:#f6f8fa;padding:5px;border-radius:5px;');
tagStyle.s = "display:inline;text-decoration:line-through;" + (tagStyle.s || "");
tagStyle.u = "display:inline;text-decoration:underline;" + (tagStyle.u || "");
//低版本兼容
if (!CanIUse) {
blockTag.caption = true;
tagStyle.big = "display:inline;font-size:1.2em;" + (tagStyle.big || "");
tagStyle.small = "display:inline;font-size:0.8em;" + (tagStyle.small || "");
tagStyle.pre = "font-family:monospace;white-space:pre;" + tagStyle.pre;
}
return tagStyle;
}
function DomHandler(style, tagStyle = {}) {
this.imgList = [];
this.nodes = [];
this.title = "";
this._videoNum = 0;
this._audioNum = 0;
this._style = new CssTokenizer(style, initStyle(tagStyle)).parse();
this._tagStack = [];
}
DomHandler.prototype._addDomElement = function(element) {
let parent = this._tagStack[this._tagStack.length - 1];
let siblings = parent ? parent.children : this.nodes;
siblings.push(element);
};
DomHandler.prototype._bubbling = function() {
for (let i = this._tagStack.length - 1; i >= 0; i--) {
if (trustTag[this._tagStack[i].name] == Common) this._tagStack[i].continue = true;
else return this._tagStack[i].name;
}
}
DomHandler.prototype.onopentag = function(name, attrs) {
let element = {
children: []
};
//匹配样式
let matched =
(this._style[name] ? (this._style[name] + ';') : '') +
(this._style['#' + attrs.id] ? (this._style['#' + attrs.id] + ';') : '') +
(this._style['.' + attrs.class] ? (this._style['.' + attrs.class] + ';') : '');
delete attrs.class;
delete attrs.id;
//处理属性
switch (name) {
case 'div':
case 'p':
if (attrs.align) {
attrs.style += (';text-align:' + attrs.align);
delete attrs.align;
}
break;
case 'img':
if (attrs.width) {
attrs.style = 'width:' + attrs.width + (/[0-9]/.test(attrs.width[attrs.width.length - 1]) ? 'px' : '') + ';' + attrs.style;
delete attrs.width;
}
if (attrs['data-src']) {
attrs.src = attrs.src || attrs['data-src'];
delete attrs['data-src'];
}
if (!attrs.hasOwnProperty('ignore') && attrs.src) {
this.imgList.push(attrs.src);
if (this._bubbling() == 'a') attrs.ignore = "";
};
break;
case 'font':
name = 'span';
if (attrs.color) {
attrs.style += (';color:' + attrs.color);
delete attrs.color;
}
if (attrs.face) {
attrs.style += (";font-family:" + attrs.face);
delete attrs.face;
}
break;
case 'a':
this._bubbling();
break;
case 'video':
case 'audio':
attrs.loop = attrs.hasOwnProperty('loop');
attrs.controls = attrs.hasOwnProperty('controls');
attrs.autoplay = attrs.hasOwnProperty('autoplay');
if (name == 'video') {
attrs.muted = attrs.hasOwnProperty('muted');
if (attrs.width) {
attrs.style = 'width:' + parseFloat(attrs.width) + 'px;' + attrs.style;
delete attrs.width;
}
if (attrs.height) {
attrs.style = 'height:' + parseFloat(attrs.height) + 'px;' + attrs.style;
delete attrs.height;
}
}
attrs.id = (name + (++this['_' + name + 'Num']));
attrs.source = [];
if (attrs.src) attrs.source.push(attrs.src);
if (!attrs.controls && !attrs.autoplay)
console.warn('存在没有controls属性的' + name + '标签,可能导致无法播放', attrs);
this._bubbling();
break;
case 'source':
let parent = this._tagStack[this._tagStack.length - 1];
if (parent && (parent.name == 'video' || parent.name == 'audio')) {
parent.attrs.source.push(attrs.src);
if (!parent.attrs.src) parent.attrs.src = attrs.src;
}
this._tagStack.push(element);
return;
}
attrs.style = matched + attrs.style;
if (textTag[name]) element.continue = true;
else if (blockTag[name]) name = 'div';
else if (!trustTag[name]) name = 'span';
element.name = name;
element.attrs = attrs;
this._addDomElement(element);
this._tagStack.push(element);
};
DomHandler.prototype.ontext = function(data) {
if (/\S/.test(data)) {
let element = {
text: data.replace(/&nbsp;/g, '\u00A0'),
type: 'text'
};
if (/&#*((?!sp|lt|gt).){2,5};/.test(data)) element.decode = true;
this._addDomElement(element);
}
};
DomHandler.prototype.onclosetag = function(name) {
let element = this._tagStack.pop();
if (ignoreTag[name]) {
if (name == 'title') {
try {
this.title = element.children[0].text;
} catch (e) {}
}
let parent = this._tagStack[this._tagStack.length - 1];
let siblings = parent ? parent.children : this.nodes;
siblings.pop();
}
};
module.exports = DomHandler;
\ No newline at end of file
... ...
//Parser.js
const Tokenizer = require("./Tokenizer.js");
const DomHandler = require("./DomHandler.js");
const trustAttrs = {
align: true,
alt: true,
author: true,
autoplay: true,
class: true,
color: true,
colspan: true,
controls: true,
"data-src": true,
dir: true,
face: true,
height: true,
href: true,
id: true,
ignore: true,
loop: true,
muted: true,
name: true,
poster: true,
rowspan: true,
span: true,
src: true,
start: true,
style: true,
type: true,
width: true,
};
const voidTag = {
area: true,
base: true,
basefont: true,
br: true,
col: true,
circle: true,
command: true,
ellipse: true,
embed: true,
frame: true,
hr: true,
img: true,
input: true,
isindex: true,
keygen: true,
line: true,
link: true,
meta: true,
param: true,
path: true,
polygon: true,
polyline: true,
rect: true,
source: true,
stop: true,
track: true,
use: true,
wbr: true
};
function Parser(cbs, callback) {
this._cbs = cbs;
this._callback = callback;
this._tagname = "";
this._attribname = "";
this._attribvalue = "";
this._attribs = null;
this._stack = [];
this._tokenizer = new Tokenizer(this);
}
Parser.prototype.ontext = function(data) {
this._cbs.ontext(data);
};
Parser.prototype.onopentagname = function(name) {
name = name.toLowerCase();
this._tagname = name;
this._attribs = {
style: ''
};
if (!voidTag[name]) this._stack.push(name);
};
Parser.prototype.onopentagend = function() {
if (this._attribs) {
this._cbs.onopentag(this._tagname, this._attribs);
this._attribs = null;
}
if (voidTag[this._tagname]) this._cbs.onclosetag(this._tagname);
this._tagname = "";
};
Parser.prototype.onclosetag = function(name) {
name = name.toLowerCase();
if (this._stack.length && !voidTag[name]) {
var pos = this._stack.lastIndexOf(name);
if (pos !== -1) {
pos = this._stack.length - pos;
while (pos--) this._cbs.onclosetag(this._stack.pop());
} else if (name === "p") {
this.onopentagname(name);
this._closeCurrentTag();
}
} else if (name === "br" || name === "hr" || name === "p") {
this.onopentagname(name);
this._closeCurrentTag();
}
};
Parser.prototype._closeCurrentTag = function() {
let name = this._tagname;
this.onopentagend();
if (this._stack[this._stack.length - 1] === name) {
this._cbs.onclosetag(name);
this._stack.pop();
}
};
Parser.prototype.onattribend = function() {
this._attribvalue = this._attribvalue.replace(/&quot;/g, '"');
if (this._attribs && trustAttrs[this._attribname]) {
this._attribs[this._attribname] = this._attribvalue;
}
this._attribname = "";
this._attribvalue = "";
};
Parser.prototype.onend = function() {
for (
var i = this._stack.length; i > 0; this._cbs.onclosetag(this._stack[--i])
);
this._callback({
'nodes': this._cbs.nodes,
'title': this._cbs.title,
'imgList': this._cbs.imgList
});
};
Parser.prototype.write = function(chunk) {
this._tokenizer.parse(chunk);
};
function html2nodes(data, tagStyle) {
return new Promise(function(resolve, reject) {
try {
let style = '';
data = data.replace(/<style.*?>([\s\S]*?)<\/style>/gi, function() {
style += arguments[1];
return '';
});
let handler = new DomHandler(style, tagStyle);
new Parser(handler, (res) => {
return resolve(res);
}).write(data);
} catch (err) {
return reject(err);
}
})
}
module.exports = html2nodes;
\ No newline at end of file
... ...
//Tokenizer.js
function Tokenizer(cbs) {
this._state = "TEXT";
this._buffer = "";
this._sectionStart = 0;
this._index = 0;
this._cbs = cbs;
}
Tokenizer.prototype.TEXT = function(c) {
var index = this._buffer.indexOf("<", this._index);
if (index != -1) {
this._index = index;
this._cbs.ontext(this._getSection());
this._state = "BeforeTag";
this._sectionStart = this._index;
} else this._index = this._buffer.length;
};
Tokenizer.prototype.BeforeTag = function(c) {
switch (c) {
case "/":
this._state = "BeforeCloseTag";
break;
case "!":
this._state = "BeforeDeclaration";
break;
case "?":
let index = this._buffer.indexOf(">", this._index);
if (index != -1) {
this._index = index;
this._sectionStart = this._index + 1;
} else this._sectionStart = this._index = this._buffer.length;
this._state = "TEXT";
break;
case ">":
this._state = "TEXT";
break;
case "<":
this._cbs.ontext(this._getSection());
this._sectionStart = this._index;
break;
default:
if (/\s/.test(c)) this._state = "TEXT";
else {
this._state = "InTag";
this._sectionStart = this._index;
}
}
};
Tokenizer.prototype.InTag = function(c) {
if (c === "/" || c === ">" || /\s/.test(c)) {
this._cbs.onopentagname(this._getSection());
this._state = "BeforeAttrsName";
this._index--;
}
};
Tokenizer.prototype.BeforeAttrsName = function(c) {
if (c === ">") {
this._cbs.onopentagend();
this._state = "TEXT";
this._sectionStart = this._index + 1;
} else if (c === "/") {
this._state = "InSelfCloseTag";
} else if (!(/\s/.test(c))) {
this._state = "InAttrsName";
this._sectionStart = this._index;
}
};
Tokenizer.prototype.InAttrsName = function(c) {
if (c === "=" || c === "/" || c === ">" || /\s/.test(c)) {
this._cbs._attribname = this._getSection().toLowerCase();
this._sectionStart = -1;
this._state = "AfterAttrsName";
this._index--;
}
};
Tokenizer.prototype.AfterAttrsName = function(c) {
if (c === "=") {
this._state = "BeforeAttrsValue";
} else if (c === "/" || c === ">") {
this._cbs.onattribend();
this._state = "BeforeAttrsName";
this._index--;
} else if (!(/\s/.test(c))) {
this._cbs.onattribend();
this._state = "InAttrsName";
this._sectionStart = this._index;
}
};
Tokenizer.prototype.BeforeAttrsValue = function(c) {
if (c === '"') {
this._state = "InAttrsValueDQ";
this._sectionStart = this._index + 1;
} else if (c === "'") {
this._state = "InAttrsValueSQ";
this._sectionStart = this._index + 1;
} else if (!(/\s/.test(c))) {
this._state = "InAttrsValueNQ";
this._sectionStart = this._index;
this._index--;
}
};
Tokenizer.prototype.InAttrsValueDQ = function(c) {
if (c === '"') {
this._cbs._attribvalue += this._getSection();
this._cbs.onattribend();
this._state = "BeforeAttrsName";
}
};
Tokenizer.prototype.InAttrsValueSQ = function(c) {
if (c === "'") {
this._cbs._attribvalue += this._getSection();
this._cbs.onattribend();
this._state = "BeforeAttrsName";
}
};
Tokenizer.prototype.InAttrsValueNQ = function(c) {
if (/\s/.test(c) || c === ">") {
this._cbs._attribvalue += this._getSection();
this._cbs.onattribend();
this._state = "BeforeAttrsName";
this._index--;
}
};
Tokenizer.prototype.BeforeCloseTag = function(c) {
if (/\s/.test(c));
else if (c === ">") {
this._state = "TEXT";
} else {
this._state = "InCloseTag";
this._sectionStart = this._index;
}
};
Tokenizer.prototype.InCloseTag = function(c) {
if (c === ">" || /\s/.test(c)) {
this._cbs.onclosetag(this._getSection());
this._state = "AfterCloseTag";
this._index--;
}
};
Tokenizer.prototype.InSelfCloseTag = function(c) {
if (c === ">") {
this._cbs.onopentagend();
this._state = "TEXT";
this._sectionStart = this._index + 1;
} else if (!(/\s/.test(c))) {
this._state = "BeforeAttrsName";
this._index--;
}
};
Tokenizer.prototype.AfterCloseTag = function(c) {
if (c === ">") {
this._state = "TEXT";
this._sectionStart = this._index + 1;
}
};
Tokenizer.prototype.BeforeDeclaration = function(c) {
if (c == '-') this._state = "InComment";
else if (c == '[') this._state = "BeforeCDATA1";
else this._state = "InDeclaration";
};
Tokenizer.prototype.InDeclaration = function(c) {
var index = this._buffer.indexOf(">", this._index);
if (index != -1) {
this._index = index;
this._sectionStart = index + 1;
} else this._sectionStart = this._index = this._buffer.length;
this._state = "TEXT";
};
Tokenizer.prototype.InComment = function(c) {
let key = (c == '-' ? '-->' : '>');
let index = this._buffer.indexOf(key, this._index);
if (index != -1) {
this._index = index + key.length - 1;
this._sectionStart = this._index + 1;
} else this._sectionStart = this._index = this._buffer.length;
this._state = "TEXT";
};
Tokenizer.prototype.BeforeCDATA1 = function(c) {
if (c == 'C') this._state = "BeforeCDATA2";
else this._state = "InDeclaration";
};
Tokenizer.prototype.BeforeCDATA2 = function(c) {
if (c == 'D') this._state = "BeforeCDATA3";
else this._state = "InDeclaration";
};
Tokenizer.prototype.BeforeCDATA3 = function(c) {
if (c == 'A') this._state = "BeforeCDATA4";
else this._state = "InDeclaration";
};
Tokenizer.prototype.BeforeCDATA4 = function(c) {
if (c == 'T') this._state = "BeforeCDATA5";
else this._state = "InDeclaration";
};
Tokenizer.prototype.BeforeCDATA5 = function(c) {
if (c == 'A') this._state = "InCDATA";
else this._state = "InDeclaration";
};
Tokenizer.prototype.InCDATA = function(c) {
let key = (c == '[' ? ']]>' : '>');
let index = this._buffer.indexOf(key, this._index);
if (index != -1) {
this._index = index + key.length - 1;
this._sectionStart = this._index + 1;
} else this._sectionStart = this._index = this._buffer.length;
this._state = "TEXT";
};
Tokenizer.prototype.parse = function(chunk) {
this._buffer += chunk;
for (; this._index < this._buffer.length; this._index++)
this[this._state](this._buffer[this._index]);
if (this._state === "TEXT" && this._sectionStart !== this._index)
this._cbs.ontext(this._buffer.substr(this._sectionStart));
this._cbs.onend();
};
Tokenizer.prototype._getSection = function() {
return this._buffer.substring(this._sectionStart, this._index);
};
module.exports = Tokenizer;
\ No newline at end of file
... ...
String.prototype.splice = function(start = 0, deleteCount = 0, addStr = '') {
if (start < 0) start = this.length + start;
if (deleteCount < 0) deleteCount = 0;
return this.substring(0, start) + addStr + this.substring(start + deleteCount);
}
module.exports = {
versionHigherThan(version = '') {
var v1 = wx.getSystemInfoSync().SDKVersion.split('.');
var v2 = version.split('.');
const len = Math.max(v1.length, v2.length);
while (v1.length < len) {
v1.push('0');
}
while (v2.length < len) {
v2.push('0');
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i]);
const num2 = parseInt(v2[i]);
if (num1 > num2) {
return true;
} else if (num1 < num2) {
return false;
}
}
return true;
},
html2nodes(html, tagStyle) {
const Parser = require('./Parser.js');
return Parser(html, tagStyle);
},
css2object(style, tagStyle) {
const CssTokenizer = require('./CssTokenizer.js');
return new CssTokenizer(style, tagStyle).parse();
}
}
\ No newline at end of file
... ...
//Parser组件
const html2nodes = require('./Parser.js');
const initData = function(Component) {
setTimeout(() => {
Component.createSelectorQuery().select('#contain').boundingClientRect(res => {
Component.triggerEvent('ready', res);
}).exec();
Component.videoContext = [];
let nodes = [Component.selectComponent('#contain')];
nodes = nodes.concat(Component.selectAllComponents('#contain>>>#node'));
for (let node of nodes) {
for (let item of node.data.nodes) {
if (item.name == 'video') {
Component.videoContext.push({
id: item.attrs.id,
context: wx.createVideoContext(item.attrs.id, node)
});
} else if (item.name == 'audio' && item.attrs.autoplay)
wx.createAudioContext(item.attrs.id, node).play();
}
}
}, 10)
}
Component({
properties: {
'html': {
type: null,
value: '',
observer: function(html) {
let hideAnimation = {},
showAnimation = {};
if (this.data.showWithAnimation) {
hideAnimation = wx.createAnimation({
duration: this.data.animationDuration,
timingFunction: "ease"
}).opacity(0).step().export();
showAnimation = wx.createAnimation({
duration: this.data.animationDuration,
timingFunction: "ease"
}).opacity(1).step().export();
}
if (!html) {
this.setData({
nodes: []
})
} else if (typeof html == 'string') {
html2nodes(html, this.data.tagStyle).then(res => {
this.setData({
nodes: res.nodes,
controls: {
imgMode: this.data.imgMode
},
showAnimation,
hideAnimation
}, initData(this))
if (res.title) {
wx.setNavigationBarTitle({
title: res.title
})
}
this.imgList = res.imgList;
this.triggerEvent('parse', res);
}).catch(err => {
this.triggerEvent('error', err);
})
} else if (html.constructor == Array) {
this.setData({
controls: {
imgMode: this.data.imgMode
},
showAnimation,
hideAnimation
}, initData(this))
this.imgList = [];
} else if (typeof html == 'object') {
if (!html.nodes || html.nodes.constructor != Array) {
this.triggerEvent('error', {
message: "传入的nodes数组格式不正确!应该传入的类型是array,实际传入的类型是:" + typeof html.nodes
});
return;
}
this.setData({
controls: {
imgMode: this.data.imgMode
},
showAnimation,
hideAnimation
}, initData(this))
if (html.title) {
wx.setNavigationBarTitle({
title: html.title
})
}
this.imgList = html.imgList || [];
} else {
this.triggerEvent('error', {
message: "错误的html类型:" + typeof html
});
}
}
},
'autocopy': {
type: Boolean,
value: true
},
'autopause': {
type: Boolean,
value: true
},
'imgMode': {
type: String,
value: "default"
},
'selectable': {
type: Boolean,
value: false
},
'tagStyle': {
type: Object,
value: {}
},
'showWithAnimation': {
type: Boolean,
value: false
},
'animationDuration': {
type: Number,
value: 400
}
},
methods: {
//事件
tapEvent(e) {
if (this.data.autocopy && e.detail && /^http/.test(e.detail)) {
wx.setClipboardData({
data: e.detail,
success() {
wx.showToast({
title: '链接已复制',
})
}
})
}
this.triggerEvent('linkpress', e.detail);
},
errorEvent(e) {
this.triggerEvent('error', e.detail);
},
//内部方法
_previewImg(e) {
wx.previewImage({
current: e.detail,
urls: this.imgList.length ? this.imgList : [e.detail],
})
},
_playVideo(e) {
if (this.videoContext.length > 1 && this.data.autopause) {
for (let video of this.videoContext) {
if (video.id == e.detail) continue;
video.context.pause();
}
}
}
}
})
\ No newline at end of file
... ...
{
"component": true,
"usingComponents": {
"trees": "./trees/trees"
}
}
\ No newline at end of file
... ...
<!--Parser-->
<slot wx:if="{{!(html.nodes||((html[0].name||html[0].type)?1:nodes.length))}}" animation="{{hideAnimation}}"></slot>
<trees id="contain" style="{{(showWithAnimation?'opacity:0;':'')+(selectable?'user-select:text;-webkit-user-select:text':'')}}" animation="{{showAnimation}}" nodes="{{html.nodes||((html[0].name||html[0].type)?html:nodes)}}" controls="{{controls}}" catchpreview="_previewImg"
catchplay="_playVideo" catchlinkpress="tapEvent" catcherror="errorEvent" />
\ No newline at end of file
... ...
:host {
display: block;
overflow: scroll;
}
\ No newline at end of file
... ...
// Parser/trees/cssHandler.wxs
module.exports = {
getStyle: function(style, display) {
var res = "";
var reg = getRegExp("float\s*:\s*[^;]*", "i");
if (reg.test(style)) res += reg.exec(style)[0];
reg = getRegExp("display\s*:\s*[^;]*", "i");
if (reg.test(style)) res += (';' + reg.exec(style)[0]);
else res += (';display:' + display);
reg = getRegExp("[^;\s]*width\s*:\s*[^;]*", "ig");
var width = reg.exec(style);
while (width) {
res += (';' + width[0]);
width = reg.exec(style);
}
return res;
},
setImgStyle: function (item, imgMode) {
if (getRegExp("[^-]width\s*:\s*[^;]*", "i").test(';' + item.attrs.style))
item.attrs.style += ';width:100%';
if (imgMode == "widthFix")
item.attrs.style += ";height:auto !important";
return [item];
},
setStyle: function(item) {
if (getRegExp("[^-]width\s*:\s*[^;]*", "i").test(';' + item.attrs.style))
item.attrs.style += ';width:100%';
return [item];
}
}
\ No newline at end of file
... ...
// Parser/trees/trees.js
Component({
properties: {
nodes: {
type: Array,
value: []
},
controls: {
type: Object,
value: {}
}
},
methods: {
//冒泡事件
playEvent(e) {
this.triggerEvent('play', e.currentTarget.dataset.id, {
bubbles: true,
composed: true
});
},
previewEvent(e) {
if (!e.target.dataset.hasOwnProperty('ignore')) {
this.triggerEvent('preview', e.currentTarget.dataset.src, {
bubbles: true,
composed: true
});
}
},
tapEvent(e) {
this.triggerEvent('linkpress', e.currentTarget.dataset.href, {
bubbles: true,
composed: true
});
},
errorEvent(e) {
//尝试加载其他源
if (!this.data.controls[e.currentTarget.dataset.id] && e.currentTarget.dataset.source.length > 1) {
this.data.controls[e.currentTarget.dataset.id] = {
play: false,
index: 1
}
} else if (this.data.controls[e.currentTarget.dataset.id] && e.currentTarget.dataset.source.length > (this.data.controls[e.currentTarget.dataset.id].index + 1)) {
this.data.controls[e.currentTarget.dataset.id].index++;
}
this.setData({
controls: this.data.controls
})
this.triggerEvent('error', {
target: e.currentTarget,
message: e.detail.errMsg
}, {
bubbles: true,
composed: true
});
},
//内部方法:加载视频
_loadVideo(e) {
this.data.controls[e.currentTarget.dataset.id] = {
play: true,
index: 0
}
this.setData({
controls: this.data.controls
})
},
}
})
\ No newline at end of file
... ...
{
"component": true,
"usingComponents": {
"trees":"./trees"
}
}
\ No newline at end of file
... ...
<!--Parser/trees/trees.wxml-->
<wxs module="cssHandler" src="./cssHandler.wxs" />
<block wx:for='{{nodes}}' wx:key>
<block wx:if="{{!item.continue}}">
<!--图片-->
<rich-text wx:if="{{item.name=='img'}}" style="{{cssHandler.getStyle(item.attrs.style,'inline-block')}}" nodes='{{cssHandler.setImgStyle(item,controls.imgMode)}}' data-ignore='{{item.attrs.ignore}}' data-src='{{item.attrs.src}}' bindtap='previewEvent' />
<!--文本-->
<text wx:elif="{{item.type=='text'&&!item.decode}}" decode>{{item.text}}</text>
<text wx:elif="{{item.name=='br'}}">\n</text>
<!--视频-->
<block wx:elif="{{item.name=='video'}}">
<view wx:if="{{item.attrs.id>'video3'&&!controls[item.attrs.id].play}}" class="video" style="{{item.attrs.style+(item.attrs.width?(';width:'+item.attrs.width):'')+(item.attrs.height?('height:'+item.attrs.height):'')}}" data-id="{{item.attrs.id}}" bindtap="_loadVideo">
<view class="video-triangle" />
</view>
<video wx:else src='{{controls[item.attrs.id]?item.attrs.source[controls[item.attrs.id].index]:item.attrs.src}}' id="{{item.attrs.id}}" loop='{{item.attrs.loop}}' controls='{{item.attrs.controls}}' autoplay="{{item.attrs.autoplay||controls[item.attrs.id].play}}"
class="v" muted="{{item.attrs.muted}}" style="{{item.attrs.style+(item.attrs.width?(';width:'+item.attrs.width):'')+(item.attrs.height?('height:'+item.attrs.height):'')}}" data-id="{{item.attrs.id}}" data-source="{{item.attrs.source}}" bindplay='playEvent'
binderror="errorEvent" />
</block>
<!--音频-->
<audio wx:elif="{{item.name=='audio'}}" src='{{controls[item.attrs.id]?item.attrs.source[controls[item.attrs.id].index]:item.attrs.src}}' id="{{item.attrs.id}}" loop='{{item.attrs.loop}}' controls='{{item.attrs.controls}}' poster='{{item.attrs.poster}}'
name='{{item.attrs.name}}' author='{{item.attrs.author}}' style="{{item.attrs.style}}" data-id="{{item.attrs.id}}" data-source="{{item.attrs.source}}" binderror="errorEvent" />
<!--富文本-->
<rich-text wx:else class="{{item.name||item.type}}" style="{{cssHandler.getStyle(item.attrs.style,'block')}}" nodes="{{cssHandler.setStyle(item)}}" />
</block>
<!--链接-->
<navigator wx:elif="{{item.name=='a'}}" url="{{item.attrs.href}}" style="{{item.attrs.style}}" data-href='{{item.attrs.href}}' hover-class="navigator-hover" bindtap="tapEvent">
<trees id="node" nodes="{{item.children}}" controls="{{controls}}" />
</navigator>
<!--继续递归-->
<trees wx:else id="node" class="{{item.name}}" style="{{item.attrs.style}}" nodes="{{item.children}}" controls="{{controls}}" />
</block>
\ No newline at end of file
... ...
/* 链接受到点击的hover-class,可自定义修改 */
.navigator-hover {
opacity: 0.7;
text-decoration: underline;
}
/* 以下内容不建议修改 */
:host {
display: inherit;
float: inherit;
}
.sub, .sup, .text, .bdo, .bdi, .ruby, .rt, .span {
display: inline-block !important;
}
.div, .blockquote, .p{
display:block;
}
.b, .strong {
display: inline;
font-weight: bold;
}
.em, .i {
display: inline;
font-style: italic;
}
.del {
display: inline;
text-decoration: line-through;
}
.ins {
display: inline;
text-decoration: underline;
}
.code {
display: inline;
font-family: monospace;
}
.big {
font-size:1.2em;
display:inline;
}
.small {
font-size:0.8em;
display:inline;
}
.q, .span, .label, .abbr {
display: inline;
}
.q::before {
content: '"';
}
.q::after {
content: '"';
}
.video {
background-color: black;
width: 300px;
height: 225px;
display: inline-block;
position: relative;
}
.video-triangle {
border-width: 15px 0 15px 30px;
border-style: solid;
border-color: transparent transparent transparent white;
position: absolute;
left: 50%;
top: 50%;
margin: -15px 0 0 -15px;
}
\ No newline at end of file
... ...