html.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. function _getCssList(css) {
  2. css = css.replace(/"/g, '"');
  3. var list = {},
  4. reg = /\s*([\w\-]+)\s*:([^;]*)(;|$)/g,
  5. match;
  6. while ((match = reg.exec(css))) {
  7. var key = _trim(match[1].toLowerCase()),
  8. val = _trim(_toHex(match[2]));
  9. list[key] = val;
  10. }
  11. return list;
  12. }
  13. function _getAttrList(tag) {
  14. var list = {},
  15. reg = /\s+(?:([\w\-:]+)|(?:([\w\-:]+)=([^\s"'<>]+))|(?:([\w\-:"]+)="([^"]*)")|(?:([\w\-:"]+)='([^']*)'))(?=(?:\s|\/|>)+)/g,
  16. match;
  17. while ((match = reg.exec(tag))) {
  18. var key = (match[1] || match[2] || match[4] || match[6]).toLowerCase(),
  19. val = (match[2] ? match[3] : (match[4] ? match[5] : match[7])) || '';
  20. list[key] = val;
  21. }
  22. return list;
  23. }
  24. function _addClassToTag(tag, className) {
  25. if (/\s+class\s*=/.test(tag)) {
  26. tag = tag.replace(/(\s+class=["']?)([^"']*)(["']?[\s>])/, function($0, $1, $2, $3) {
  27. if ((' ' + $2 + ' ').indexOf(' ' + className + ' ') < 0) {
  28. return $2 === '' ? $1 + className + $3 : $1 + $2 + ' ' + className + $3;
  29. } else {
  30. return $0;
  31. }
  32. });
  33. } else {
  34. tag = tag.substr(0, tag.length - 1) + ' class="' + className + '">';
  35. }
  36. return tag;
  37. }
  38. function _formatCss(css) {
  39. var str = '';
  40. _each(_getCssList(css), function(key, val) {
  41. str += key + ':' + val + ';';
  42. });
  43. return str;
  44. }
  45. function _formatUrl(url, mode, host, pathname) {
  46. mode = _undef(mode, '').toLowerCase();
  47. // 移除连续斜线,比如,http://localhost/upload/file/201205//maincus.swf
  48. // base64 data 除外
  49. if (url.substr(0, 5) != 'data:') {
  50. url = url.replace(/([^:])\/\//g, '$1/');
  51. }
  52. if (_inArray(mode, ['absolute', 'relative', 'domain']) < 0) {
  53. return url;
  54. }
  55. host = host || location.protocol + '//' + location.host;
  56. if (pathname === undefined) {
  57. var m = location.pathname.match(/^(\/.*)\//);
  58. pathname = m ? m[1] : '';
  59. }
  60. var match;
  61. if ((match = /^(\w+:\/\/[^\/]*)/.exec(url))) {
  62. if (match[1] !== host) {
  63. return url;
  64. }
  65. } else if (/^\w+:/.test(url)) {
  66. return url;
  67. }
  68. function getRealPath(path) {
  69. var parts = path.split('/'), paths = [];
  70. for (var i = 0, len = parts.length; i < len; i++) {
  71. var part = parts[i];
  72. if (part == '..') {
  73. if (paths.length > 0) {
  74. paths.pop();
  75. }
  76. } else if (part !== '' && part != '.') {
  77. paths.push(part);
  78. }
  79. }
  80. return '/' + paths.join('/');
  81. }
  82. if (/^\//.test(url)) {
  83. url = host + getRealPath(url.substr(1));
  84. } else if (!/^\w+:\/\//.test(url)) {
  85. url = host + getRealPath(pathname + '/' + url);
  86. }
  87. function getRelativePath(path, depth) {
  88. if (url.substr(0, path.length) === path) {
  89. var arr = [];
  90. for (var i = 0; i < depth; i++) {
  91. arr.push('..');
  92. }
  93. var prefix = '.';
  94. if (arr.length > 0) {
  95. prefix += '/' + arr.join('/');
  96. }
  97. if (pathname == '/') {
  98. prefix += '/';
  99. }
  100. return prefix + url.substr(path.length);
  101. } else {
  102. if ((match = /^(.*)\//.exec(path))) {
  103. return getRelativePath(match[1], ++depth);
  104. }
  105. }
  106. }
  107. if (mode === 'relative') {
  108. url = getRelativePath(host + pathname, 0).substr(2);
  109. } else if (mode === 'absolute') {
  110. if (url.substr(0, host.length) === host) {
  111. url = url.substr(host.length);
  112. }
  113. }
  114. return url;
  115. }
  116. function _formatHtml(html, htmlTags, urlType, wellFormatted, indentChar) {
  117. // null or undefined: object == null
  118. if (html == null) {
  119. html = '';
  120. }
  121. urlType = urlType || '';
  122. wellFormatted = _undef(wellFormatted, false);
  123. indentChar = _undef(indentChar, '\t');
  124. var fontSizeList = 'xx-small,x-small,small,medium,large,x-large,xx-large'.split(',');
  125. // 将pre里的br转换成\n
  126. html = html.replace(/(<(?:pre|pre\s[^>]*)>)([\s\S]*?)(<\/pre>)/ig, function($0, $1, $2, $3) {
  127. return $1 + $2.replace(/<(?:br|br\s[^>]*)>/ig, '\n') + $3;
  128. });
  129. // <br/></p> to </p>
  130. html = html.replace(/<(?:br|br\s[^>]*)\s*\/?>\s*<\/p>/ig, '</p>');
  131. // <p></p> to <p><br /></p>
  132. html = html.replace(/(<(?:p|p\s[^>]*)>)\s*(<\/p>)/ig, '$1<br />$2');
  133. // empty char
  134. html = html.replace(/\u200B/g, '');
  135. // &copy;
  136. html = html.replace(/\u00A9/g, '&copy;');
  137. // &reg;
  138. html = html.replace(/\u00AE/g, '&reg;');
  139. // Bugfix:
  140. // https://github.com/kindsoft/kindeditor/issues/147
  141. html = html.replace(/\u2003/g, '&emsp;');
  142. html = html.replace(/\u3000/g, '&emsp;');
  143. // Bugfix:
  144. // https://github.com/kindsoft/kindeditor/issues/116
  145. // https://github.com/kindsoft/kindeditor/issues/145
  146. html = html.replace(/<[^>]+/g, function($0) {
  147. return $0.replace(/\s+/g, ' ');
  148. });
  149. var htmlTagMap = {};
  150. if (htmlTags) {
  151. // 展开htmlTags里的key
  152. _each(htmlTags, function(key, val) {
  153. var arr = key.split(',');
  154. for (var i = 0, len = arr.length; i < len; i++) {
  155. htmlTagMap[arr[i]] = _toMap(val);
  156. }
  157. });
  158. // 删除script和style里的内容
  159. if (!htmlTagMap.script) {
  160. html = html.replace(/(<(?:script|script\s[^>]*)>)([\s\S]*?)(<\/script>)/ig, '');
  161. }
  162. if (!htmlTagMap.style) {
  163. html = html.replace(/(<(?:style|style\s[^>]*)>)([\s\S]*?)(<\/style>)/ig, '');
  164. }
  165. }
  166. var re = /(\s*)<(\/)?([\w\-:]+)((?:\s+|(?:\s+[\w\-:]+)|(?:\s+[\w\-:]+=[^\s"'<>]+)|(?:\s+[\w\-:"]+="[^"]*")|(?:\s+[\w\-:"]+='[^']*'))*)(\/)?>(\s*)/g;
  167. var tagStack = [];
  168. html = html.replace(re, function($0, $1, $2, $3, $4, $5, $6) {
  169. var full = $0,
  170. startNewline = $1 || '',
  171. startSlash = $2 || '',
  172. tagName = $3.toLowerCase(),
  173. attr = $4 || '',
  174. endSlash = $5 ? ' ' + $5 : '',
  175. endNewline = $6 || '';
  176. // 不在名单里的过滤掉
  177. if (htmlTags && !htmlTagMap[tagName]) {
  178. return '';
  179. }
  180. // 无闭合标签的自动添加斜线
  181. if (endSlash === '' && _SINGLE_TAG_MAP[tagName]) {
  182. endSlash = ' /';
  183. }
  184. // inline tag时自动将多个空白转换成一个空格
  185. if (_INLINE_TAG_MAP[tagName]) {
  186. if (startNewline) {
  187. startNewline = ' ';
  188. }
  189. if (endNewline) {
  190. endNewline = ' ';
  191. }
  192. }
  193. // pre,style,script tag的格式化
  194. if (_PRE_TAG_MAP[tagName]) {
  195. if (startSlash) {
  196. endNewline = '\n';
  197. } else {
  198. startNewline = '\n';
  199. }
  200. }
  201. // br tag
  202. if (wellFormatted && tagName == 'br') {
  203. endNewline = '\n';
  204. }
  205. // block tag的格式化
  206. if (_BLOCK_TAG_MAP[tagName] && !_PRE_TAG_MAP[tagName]) {
  207. if (wellFormatted) {
  208. if (startSlash && tagStack.length > 0 && tagStack[tagStack.length - 1] === tagName) {
  209. tagStack.pop();
  210. } else {
  211. tagStack.push(tagName);
  212. }
  213. startNewline = '\n';
  214. endNewline = '\n';
  215. for (var i = 0, len = startSlash ? tagStack.length : tagStack.length - 1; i < len; i++) {
  216. startNewline += indentChar;
  217. if (!startSlash) {
  218. endNewline += indentChar;
  219. }
  220. }
  221. if (endSlash) {
  222. tagStack.pop();
  223. } else if (!startSlash) {
  224. endNewline += indentChar;
  225. }
  226. } else {
  227. startNewline = endNewline = '';
  228. }
  229. }
  230. if (attr !== '') {
  231. var attrMap = _getAttrList(full);
  232. // 将font tag转换成span tag
  233. if (tagName === 'font') {
  234. var fontStyleMap = {}, fontStyle = '';
  235. _each(attrMap, function(key, val) {
  236. if (key === 'color') {
  237. fontStyleMap.color = val;
  238. delete attrMap[key];
  239. }
  240. if (key === 'size') {
  241. fontStyleMap['font-size'] = fontSizeList[parseInt(val, 10) - 1] || '';
  242. delete attrMap[key];
  243. }
  244. if (key === 'face') {
  245. fontStyleMap['font-family'] = val;
  246. delete attrMap[key];
  247. }
  248. if (key === 'style') {
  249. fontStyle = val;
  250. }
  251. });
  252. if (fontStyle && !/;$/.test(fontStyle)) {
  253. fontStyle += ';';
  254. }
  255. _each(fontStyleMap, function(key, val) {
  256. if (val === '') {
  257. return;
  258. }
  259. if (/\s/.test(val)) {
  260. val = "'" + val + "'";
  261. }
  262. fontStyle += key + ':' + val + ';';
  263. });
  264. attrMap.style = fontStyle;
  265. }
  266. // 处理attribute和style
  267. _each(attrMap, function(key, val) {
  268. // 补全单独属性
  269. if (_FILL_ATTR_MAP[key]) {
  270. attrMap[key] = key;
  271. }
  272. // 处理URL
  273. if (_inArray(key, ['src', 'href']) >= 0) {
  274. attrMap[key] = _formatUrl(val, urlType);
  275. }
  276. // 过滤属性
  277. if (htmlTags && key !== 'style' && !htmlTagMap[tagName]['*'] && !htmlTagMap[tagName][key] ||
  278. tagName === 'body' && key === 'contenteditable' ||
  279. /^kindeditor_\d+$/.test(key)) {
  280. delete attrMap[key];
  281. }
  282. if (key === 'style' && val !== '') {
  283. var styleMap = _getCssList(val);
  284. _each(styleMap, function(k, v) {
  285. // 过滤样式
  286. if (htmlTags && !htmlTagMap[tagName].style && !htmlTagMap[tagName]['.' + k]) {
  287. delete styleMap[k];
  288. }
  289. });
  290. var style = '';
  291. _each(styleMap, function(k, v) {
  292. style += k + ':' + v + ';';
  293. });
  294. attrMap.style = style;
  295. }
  296. });
  297. attr = '';
  298. _each(attrMap, function(key, val) {
  299. if (key === 'style' && val === '') {
  300. return;
  301. }
  302. val = val.replace(/"/g, '&quot;');
  303. attr += ' ' + key + '="' + val + '"';
  304. });
  305. }
  306. if (tagName === 'font') {
  307. tagName = 'span';
  308. }
  309. return startNewline + '<' + startSlash + tagName + attr + endSlash + '>' + endNewline;
  310. });
  311. // 将pre里的\n转换成 临时标签 + \n,防止被替换
  312. html = html.replace(/(<(?:pre|pre\s[^>]*)>)([\s\S]*?)(<\/pre>)/ig, function($0, $1, $2, $3) {
  313. return $1 + $2.replace(/\n/g, '<span id="__kindeditor_pre_newline__">\n') + $3;
  314. });
  315. html = html.replace(/\n\s*\n/g, '\n');
  316. // 删除临时标签
  317. html = html.replace(/<span id="__kindeditor_pre_newline__">\n/g, '\n');
  318. return _trim(html);
  319. }
  320. // 清理MS Word专用标签
  321. function _clearMsWord(html, htmlTags) {
  322. html = html.replace(/<meta[\s\S]*?>/ig, '')
  323. .replace(/<![\s\S]*?>/ig, '')
  324. .replace(/<style[^>]*>[\s\S]*?<\/style>/ig, '')
  325. .replace(/<script[^>]*>[\s\S]*?<\/script>/ig, '')
  326. .replace(/<w:[^>]+>[\s\S]*?<\/w:[^>]+>/ig, '')
  327. .replace(/<o:[^>]+>[\s\S]*?<\/o:[^>]+>/ig, '')
  328. .replace(/<xml>[\s\S]*?<\/xml>/ig, '')
  329. .replace(/<(?:table|td)[^>]*>/ig, function(full) {
  330. return full.replace(/border-bottom:([#\w\s]+)/ig, 'border:$1');
  331. });
  332. return _formatHtml(html, htmlTags);
  333. }
  334. // 根据URL判断 media type
  335. function _mediaType(src) {
  336. if (/\.(rm|rmvb)(\?|$)/i.test(src)) {
  337. return 'audio/x-pn-realaudio-plugin';
  338. }
  339. if (/\.(swf|flv)(\?|$)/i.test(src)) {
  340. return 'application/x-shockwave-flash';
  341. }
  342. return 'video/x-ms-asf-plugin';
  343. }
  344. // 根据 media type取得className
  345. function _mediaClass(type) {
  346. if (/realaudio/i.test(type)) {
  347. return 'ke-rm';
  348. }
  349. if (/flash/i.test(type)) {
  350. return 'ke-flash';
  351. }
  352. return 'ke-media';
  353. }
  354. function _mediaAttrs(srcTag) {
  355. return _getAttrList(unescape(srcTag));
  356. }
  357. function _mediaEmbed(attrs) {
  358. var html = '<embed ';
  359. _each(attrs, function(key, val) {
  360. html += key + '="' + val + '" ';
  361. });
  362. html += '/>';
  363. return html;
  364. }
  365. function _mediaImg(blankPath, attrs) {
  366. var width = attrs.width,
  367. height = attrs.height,
  368. type = attrs.type || _mediaType(attrs.src),
  369. srcTag = _mediaEmbed(attrs),
  370. style = '';
  371. if (/\D/.test(width)) {
  372. style += 'width:' + width + ';';
  373. } else if (width > 0) {
  374. style += 'width:' + width + 'px;';
  375. }
  376. if (/\D/.test(height)) {
  377. style += 'height:' + height + ';';
  378. } else if (height > 0) {
  379. style += 'height:' + height + 'px;';
  380. }
  381. var html = '<img class="' + _mediaClass(type) + '" src="' + blankPath + '" ';
  382. if (style !== '') {
  383. html += 'style="' + style + '" ';
  384. }
  385. html += 'data-ke-tag="' + escape(srcTag) + '" alt="" />';
  386. return html;
  387. }
  388. // Simple JavaScript Templating
  389. // John Resig - http://ejohn.org/ - MIT Licensed
  390. // http://ejohn.org/blog/javascript-micro-templating/
  391. function _tmpl(str, data) {
  392. // Figure out if we're getting a template, or if we need to
  393. // load the template - and be sure to cache the result.
  394. var fn = new Function("obj",
  395. "var p=[],print=function(){p.push.apply(p,arguments);};" +
  396. // Introduce the data as local variables using with(){}
  397. "with(obj){p.push('" +
  398. // Convert the template into pure JavaScript
  399. str.replace(/[\r\t\n]/g, " ")
  400. .split("<%").join("\t")
  401. .replace(/((^|%>)[^\t]*)'/g, "$1\r")
  402. .replace(/\t=(.*?)%>/g, "',$1,'")
  403. .split("\t").join("');")
  404. .split("%>").join("p.push('")
  405. .split("\r").join("\\'") + "');}return p.join('');");
  406. // Provide some basic currying to the user
  407. return data ? fn(data) : fn;
  408. }
  409. K.formatUrl = _formatUrl;
  410. K.formatHtml = _formatHtml;
  411. K.getCssList = _getCssList;
  412. K.getAttrList = _getAttrList;
  413. K.mediaType = _mediaType;
  414. K.mediaAttrs = _mediaAttrs;
  415. K.mediaEmbed = _mediaEmbed;
  416. K.mediaImg = _mediaImg;
  417. K.clearMsWord = _clearMsWord;
  418. K.tmpl = _tmpl;