panlippt
Version:
运行在浏览器中的ppt 演示框架
422 lines (384 loc) • 12.7 kB
JavaScript
//系统
var fs = require('fs');
var path = require('path');
//非系统
var marked = require('./marked');
var $ = require('./helper');
var libDir = __dirname;
var rootDir = path.join(libDir, '../');
var assetsDir = path.join(rootDir, 'assets') + path.sep;
var templateDir = path.join(rootDir, 'template') + path.sep;
var EOL = '\n';
var SUB_SILDE_PLACEHOLDER = '~~panliPPT%THEO●WANG%panliPPT%~~';
var M_SUB_SILDE_PLACEHOLDER = marked(SUB_SILDE_PLACEHOLDER);
var templateFile = templateDir + 'markdown.ejs';
var defaultJSON = {
title: 'panliPPT markdown',
url: '',
speaker: '',
content: '',
theme: 'moon',
transition: 'move',
files: '',
highlightStyle: 'monokai_sublime',
headFiles: '',
usemathjax: '',
date: ''
};
marked.setOptions({
gfm: true,
// silent: true,
tables: true,
breaks: true,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
langPrefix: ''
});
var emptyFn = function (str) {
// console.log(str);
};
var parser = function (string, callback, argvObj, queryObj, commonData) {
if (typeof callback !== 'function') {
callback = emptyFn;
}
var splitReg = /\[slide\s*(.*)\]/ig;
var slidesSetting = string.match(splitReg) || [];
var contents = string.split(/\[slide.*\]/i);
// console.log(contents.length, slidesSetting);
//第一个是封面
var cover = contents.shift();
var json = parseCover(cover);
json = mix({}, json, commonData);
var config = require(path.join(libDir, '../package.json'));
json.nodeppt_version = config.version;
json.nodeppt_site = config.site;
json = mix({}, defaultJSON, json, {
query: mix(queryObj || {}, argvObj || {})
});
//优先使用url的参数来控制,使用js来做,不再使用server
['theme', 'transition'].forEach(function (v) {
if (json.query && json.query[v]) {
json[v] = json.query[v];
}
});
if (json.theme) {
json.headFiles = [json.headFiles || '', '/css/theme.' + json.theme + '.css'].join(',');
}
json.headFiles = getFilesHtml(json.headFiles);
json.files = getFilesHtml(json.files);
var slides = [];
var noteReg = /\[note\]([\s\S]+)\[\/note\]/im;
var subReg = /\[subslide\]([\s\S]+)\[\/subslide\]/im;
var subSeparator = /={5,}/;
contents.forEach(function (v, i) {
var cfg = slidesSetting[i];
cfg = cfg.match(/\[slide\s+(.+)\s?\]/);
var attrs = '';
if (cfg) {
attrs = cfg[1];
}
var note = noteReg.exec(v);
if (note) {
v = v.replace(noteReg, '');
note = marked(note[1]);
}
var subslide = subReg.exec(v);
var content = [];
if (subslide) {
subslide = subslide[1].split(subSeparator)
.filter(function (v) {
return v && v.trim();
});
if (subslide.length > 1) {
v = v.replace(subReg, EOL + SUB_SILDE_PLACEHOLDER + EOL);
subslide.forEach(function (v, i) {
content.push(marked(v));
});
} else {
v = v.replace(subReg, subslide[0]);
}
}
v = v.trim();
var tContent = '';
if (v !== SUB_SILDE_PLACEHOLDER) {
tContent = marked(v);
}
slides.push(parse(tContent, content, note, attrs));
});
//合并
json.content = slides.join(EOL);
//解析
html = $.renderFile(templateFile, json);
callback(html);
return html;
};
parser.parseCover = parseCover;
module.exports = parser;
/**
* mix
* @param {[type]} obj [description]
* @return {[type]} [description]
*/
function mix(obj) {
var i = 1,
target, key;
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (; i < arguments.length; i++) {
target = arguments[i];
for (key in target) {
if (hasOwnProperty.call(target, key)) {
obj[key] = target[key];
}
}
}
return obj;
}
/**
* 解析cover的json字符串
* @param {[type]} str [description]
* @return {[type]} [description]
*/
function parseCover(str) {
str = str.split(EOL);
var obj = {};
var reg = /(\w+)\s?:\s?(.+)/;
str.forEach(function (val) {
if (val = reg.exec(val)) {
obj[val[1]] = val[2];
}
});
return obj;
}
/**
* 解析 slide内容
* @param {Array} contArr [description]
* @param {[type]} note [description]
* @param {[type]} sAttrs 头部属性
* @return {[type]} [description]
*/
function parse(content, contArr, note, sAttrs) {
contArr = contArr.map(function (v) {
return '<div class="subSlide">' + contentParser(v) + '</div>';
});
if (content.trim() === '') {
//空的,证明全部是subslide
content = contArr.join(EOL);
} else {
content = contentParser(content);
content = content.replace(M_SUB_SILDE_PLACEHOLDER, contArr.join(EOL));
}
//处理备注的内容
if (note && note.length > 3) {
note = ['<aside class="note">', '<section>', note, '</section>', '</aside>'].join(EOL);
} else {
note = '';
}
var tagStart = '<slide class="slide' + (note ? ' hasnote' : '') + '">';
//处理[slide]的属性
if (sAttrs) {
var cls = sAttrs.match(/class=['"]?([^'"]+)['"]?/);
if (cls) {
cls = cls[1] + ' slide';
sAttrs = sAttrs.replace(cls[0], '');
} else {
cls = 'slide';
}
if (/background/.test(sAttrs) && !/background-/.test(sAttrs) || /background-image/.test(sAttrs)) {
cls += ' fill';
}
tagStart = '<slide class="' + cls + '" ' + sAttrs + '>';
// console.log(tagStart);
}
return tagStart + note + '<section class="slide-wrapper">' + content + '</section></slide>';
}
function contentParser(str) {
var arr = str.split('<hr>');
var head = '',
article = '';
if (arr.length === 2) {
head = arr[0];
article = arr[1];
} else {
article = str;
}
if (head !== '') {
head = doAttr(head);
head = ['<hgroup>', head, '</hgroup>'].join(EOL);
}
var noHead = !head;
var articleAttr;
article = doAttr(article, true, noHead);
article = ['<article>', article, '</article>'].join(EOL);
//处理文章只用h1的情况,作为标题页面
if (noHead && /<h1>.*<\/h1>/g.test(article)) {
article = article.replace(/<h1>([^<]+)(\s+\{:\&(.+)\})<\/h1>/m, function (input, text, idinput, attrs) {
articleAttr = parseAttr(idinput, attrs);
return '<h1>' + text + '</h1>';
});
if (!articleAttr) {
article = article.replace('<article>', '<article class="flexbox vcenter">');
} else {
article = article.replace('<article>', '<article ' + articleAttr + '>');
}
} else {
article = article.replace(/<(\w+)>(\s?\{:(?:\&)?([^\&].+)\})<\/\1>/gm, function (input, tag, idinput, attrs) {
articleAttr = parseAttr(idinput, attrs);
return '';
});
//特殊处理tb
article = article.replace(/<(td|p)>(.*)(\s?\{:(?:\&)?([^\&].+)\})<\/\1>/gm, function (input, tag, con, idinput, attrs) {
var a = parseAttr(idinput, attrs);
if (con.indexOf('<span') === -1) {
con = '<span>' + con + '<span>';
}
return '<' + tag + ' ' + a + '>' + con + '</' + tag + '>'
});
if (articleAttr) {
article = article.replace('<article>', '<article ' + articleAttr + '>');
}
}
article = article.replace(/<p>\s*<\/p>/, '');
article = article.replace(/<p>\s*<p>(.*?)<\/p>\s*<\/p>/, function (index, reg) {
return '<p>' + reg + '</p>';
});
return head + article;
}
/**
* 替换属性
* @param {[type]} str [description]
* @param {Boolean} isArticle [description]
* @param {[type]} noHead [description]
* @return {[type]} [description]
*/
function doAttr(str, isArticle, noHead) {
//input:## Title {:.build width="200px"}
//output: <h2 class="build" width="200px">Title</h2>
str = str.replace(/<(\w+)([^>]*)>([^<]+)(\s+\{:([^\&].+)\})<\/\1>/gm, function (input, tag, pre, text, idinput, attrs) {
var attr = parseAttr(idinput, attrs);
attr = mergeAttr(pre, attr);
return '<' + tag + ' ' + attr + '>' + text + '</' + tag + '>';
});
str = str.replace(/<(\w+)>([^<]+)<(\w+)>(.+)(\s+\{:\&(.+)\})/mg, function (input, parentTag, parentText, childTag, childText, idinput, attrs) {
var attr = parseAttr(idinput, attrs);
return '<' + parentTag + ' ' + attr + '>' + parentText + '<' + childTag + '>' + childText;
});
return str;
}
/**
* 合并attr
* @param {string} oldAttr 老属性
* @param {string} newAttr 新属性
* @return {[type]} [description]
*/
function mergeAttr(oldAttr, newAttr) {
if (!oldAttr) {
return newAttr;
}
if (!newAttr) {
return oldAttr;
}
//合并class style
var oldAttrs = String(oldAttr).split(/\s+/);
var newAttrs = String(newAttr).split(/\s+/);
var old = {},
inew = {};
var back = [];
oldAttrs.forEach(function (a) {
var v = a.split('=');
v[0] = v[0].toLowerCase();
if (v[0] === 'class' || v[0] === 'style') {
old[v[0]] = v[1].slice(1, -1).trim();
} else {
back.push(a);
}
});
newAttrs.forEach(function (a) {
var v = a.split('=');
v[0] = v[0].toLowerCase();
if (v[0] === 'class' || v[0] === 'style') {
inew[v[0]] = v[1].slice(1, -1).trim();
} else {
back.push(a);
}
});
['class', 'style'].forEach(function (v) {
//两个都存在,则合并之
switch (v) {
case 'class':
back.push('class="' + [old[v] || '', inew[v] || ''].join(' ').trim() + '"');
break;
case 'style':
back.push('style="' + [old[v] || '', inew[v] || ''].join(';') + '"');
break;
}
});
return back.join(' ');
}
/**
* 解析自定义属性
* @param {[type]} idinput [description]
* @param {[type]} attrs [description]
* @return {[type]} [description]
*/
function parseAttr(idinput, attrs) {
attrs = attrs.split(/\s+/);
// console.log(attrs);
var attrArr = [];
var idArr = [],
classArr = [];
attrs.forEach(function (attr) {
if (!!~attr.indexOf('=')) {
attrArr.push(attr);
} else if (attr) {
var ids = attr.split(/[#|.]/);
// console.log(attr);
ids.forEach(function (v) {
if (v !== '') {
(new RegExp('\\#' + v)).test(idinput) && idArr.push(v);
(new RegExp('\\.' + v)).test(idinput) && classArr.push(v);
}
});
}
});
var attr = '';
var arr = [];
if (idArr.length > 0) {
arr.push('id="' + idArr.join(' ') + '"');
}
if (classArr.length > 0) {
arr.push('class="' + classArr.join(' ') + '"');
}
if (attrArr.length > 0) {
arr.push(attrArr.join(' '));
}
attr = arr.join(' ').replace(/'/g, '\'')
.replace(/"/g, '"')
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/&/g, '&');
return attr;
}
/**
* 根据files返回html
* @param {String} files files
* @return {string} html
*/
function getFilesHtml(files) {
files = (files || '').split(/\s?,\s?/);
return files.map(function (v) {
var basename = path.basename(v);
if (/.js$/i.test(basename)) {
//js文件
return '<script src="' + v + '"></script>';
} else if (/.css$/i.test(basename)) {
//css文件
return '<link rel="stylesheet" href="' + v + '">';
}
return v;
}).join(EOL);
}
///test
// var str = fs.readFileSync('ppts/demo.md').toString();
// parser(str);