mirror of
https://github.com/showdownjs/showdown.git
synced 2024-03-22 13:30:55 +08:00
feat(converter.makeMarkdown): add an HTML to MD converter
Showdown now supports a simple HTML to Markdown converter. **Usage:** ``` var conv = new showdown.Converter(); var md = conv.makeMarkdown('<a href="/url">a link</a>'); ``` Closes #388, #233
This commit is contained in:
parent
5c0d67e04a
commit
e4b0e69724
|
@ -25,6 +25,7 @@ module.exports = function (grunt) {
|
|||
'src/helpers.js',
|
||||
'src/converter.js',
|
||||
'src/subParsers/*.js',
|
||||
'src/subParsers/makeMarkdown/*.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
dest: 'dist/<%= pkg.name %>.js'
|
||||
|
|
BIN
dist/showdown.js
vendored
BIN
dist/showdown.js
vendored
Binary file not shown.
BIN
dist/showdown.js.map
vendored
BIN
dist/showdown.js.map
vendored
Binary file not shown.
BIN
dist/showdown.min.js
vendored
BIN
dist/showdown.min.js
vendored
Binary file not shown.
BIN
dist/showdown.min.js.map
vendored
BIN
dist/showdown.min.js.map
vendored
Binary file not shown.
4676
package-lock.json
generated
Normal file
4676
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -40,7 +40,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.0.2",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-contrib-clean": "^1.0.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-jshint": "^1.1.0",
|
||||
|
@ -50,6 +50,7 @@
|
|||
"grunt-endline": "^0.6.1",
|
||||
"grunt-eslint": "^19.0.0",
|
||||
"grunt-simple-mocha": "^0.4.0",
|
||||
"jsdom": "^13.0.0",
|
||||
"load-grunt-tasks": "^3.2.0",
|
||||
"performance-now": "^2.0.0",
|
||||
"quiet-grunt": "^0.2.3",
|
||||
|
|
106
src/converter.js
106
src/converter.js
|
@ -343,6 +343,112 @@ showdown.Converter = function (converterOptions) {
|
|||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an HTML string into a markdown string
|
||||
* @param src
|
||||
* @param [HTMLParser] A WHATWG DOM and HTML parser, such as JSDOM. If none is supplied, window.document will be used.
|
||||
* @returns {string}
|
||||
*/
|
||||
this.makeMarkdown = this.makeMd = function (src, HTMLParser) {
|
||||
|
||||
// replace \r\n with \n
|
||||
src = src.replace(/\r\n/g, '\n');
|
||||
src = src.replace(/\r/g, '\n'); // old macs
|
||||
|
||||
// due to an edge case, we need to find this: > <
|
||||
// to prevent removing of non silent white spaces
|
||||
// ex: <em>this is</em> <strong>sparta</strong>
|
||||
src = src.replace(/>[ \t]+</, '>¨NBSP;<');
|
||||
|
||||
if (!HTMLParser) {
|
||||
if (window && window.document) {
|
||||
HTMLParser = window.document;
|
||||
} else {
|
||||
throw new Error('HTMLParser is undefined. If in a webworker or nodejs environment, you need to provide a WHATWG DOM and HTML such as JSDOM');
|
||||
}
|
||||
}
|
||||
|
||||
var doc = HTMLParser.createElement('div');
|
||||
doc.innerHTML = src;
|
||||
|
||||
var globals = {
|
||||
preList: substitutePreCodeTags(doc)
|
||||
};
|
||||
|
||||
// remove all newlines and collapse spaces
|
||||
clean(doc);
|
||||
|
||||
// some stuff, like accidental reference links must now be escaped
|
||||
// TODO
|
||||
// doc.innerHTML = doc.innerHTML.replace(/\[[\S\t ]]/);
|
||||
|
||||
var nodes = doc.childNodes,
|
||||
mdDoc = '';
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
mdDoc += showdown.subParser('makeMarkdown.node')(nodes[i], globals);
|
||||
}
|
||||
|
||||
function clean (node) {
|
||||
for (var n = 0; n < node.childNodes.length; ++n) {
|
||||
var child = node.childNodes[n];
|
||||
if (child.nodeType === 3) {
|
||||
if (!/\S/.test(child.nodeValue)) {
|
||||
node.removeChild(child);
|
||||
--n;
|
||||
} else {
|
||||
child.nodeValue = child.nodeValue.split('\n').join(' ');
|
||||
child.nodeValue = child.nodeValue.replace(/(\s)+/g, '$1');
|
||||
}
|
||||
} else if (child.nodeType === 1) {
|
||||
clean(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find all pre tags and replace contents with placeholder
|
||||
// we need this so that we can remove all indentation from html
|
||||
// to ease up parsing
|
||||
function substitutePreCodeTags (doc) {
|
||||
|
||||
var pres = doc.querySelectorAll('pre'),
|
||||
presPH = [];
|
||||
|
||||
for (var i = 0; i < pres.length; ++i) {
|
||||
|
||||
if (pres[i].childElementCount === 1 && pres[i].firstChild.tagName.toLowerCase() === 'code') {
|
||||
var content = pres[i].firstChild.innerHTML.trim(),
|
||||
language = pres[i].firstChild.getAttribute('data-language') || '';
|
||||
|
||||
// if data-language attribute is not defined, then we look for class language-*
|
||||
if (language === '') {
|
||||
var classes = pres[i].firstChild.className.split(' ');
|
||||
for (var c = 0; c < classes.length; ++c) {
|
||||
var matches = classes[c].match(/^language-(.+)$/);
|
||||
if (matches !== null) {
|
||||
language = matches[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unescape html entities in content
|
||||
content = showdown.helper.unescapeHTMLEntities(content);
|
||||
|
||||
presPH.push(content);
|
||||
pres[i].outerHTML = '<precode language="' + language + '" precodenum="' + i.toString() + '"></precode>';
|
||||
} else {
|
||||
presPH.push(pres[i].innerHTML);
|
||||
pres[i].innerHTML = '';
|
||||
pres[i].setAttribute('prenum', i.toString());
|
||||
}
|
||||
}
|
||||
return presPH;
|
||||
}
|
||||
|
||||
return mdDoc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set an option of this Converter instance
|
||||
* @param {string} key
|
||||
|
|
|
@ -140,6 +140,21 @@ showdown.helper.escapeCharacters = function (text, charsToEscape, afterBackslash
|
|||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unescape HTML entities
|
||||
* @param txt
|
||||
* @returns {string}
|
||||
*/
|
||||
showdown.helper.unescapeHTMLEntities = function (txt) {
|
||||
'use strict';
|
||||
|
||||
return txt
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
};
|
||||
|
||||
var rgxFindMatchPos = function (str, left, right, flags) {
|
||||
'use strict';
|
||||
var f = flags || '',
|
||||
|
@ -350,6 +365,31 @@ showdown.helper.encodeEmailAddress = function (mail) {
|
|||
return mail;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param str
|
||||
* @param targetLength
|
||||
* @param padString
|
||||
* @returns {string}
|
||||
*/
|
||||
showdown.helper.padEnd = function padEnd (str, targetLength, padString) {
|
||||
'use strict';
|
||||
/*jshint bitwise: false*/
|
||||
// eslint-disable-next-line space-infix-ops
|
||||
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
|
||||
/*jshint bitwise: true*/
|
||||
padString = String(padString || ' ');
|
||||
if (str.length > targetLength) {
|
||||
return String(str);
|
||||
} else {
|
||||
targetLength = targetLength - str.length;
|
||||
if (targetLength > padString.length) {
|
||||
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
||||
}
|
||||
return String(str) + padString.slice(0,targetLength);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* POLYFILLS
|
||||
*/
|
||||
|
|
22
src/subParsers/makeMarkdown/blockquote.js
Normal file
22
src/subParsers/makeMarkdown/blockquote.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
showdown.subParser('makeMarkdown.blockquote', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasChildNodes()) {
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
var innerTxt = showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
|
||||
if (innerTxt === '') {
|
||||
continue;
|
||||
}
|
||||
txt += innerTxt;
|
||||
}
|
||||
}
|
||||
// cleanup
|
||||
txt = txt.trim();
|
||||
txt = '> ' + txt.split('\n').join('\n> ');
|
||||
return txt;
|
||||
});
|
7
src/subParsers/makeMarkdown/codeBlock.js
Normal file
7
src/subParsers/makeMarkdown/codeBlock.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
showdown.subParser('makeMarkdown.codeBlock', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var lang = node.getAttribute('language'),
|
||||
num = node.getAttribute('precodenum');
|
||||
return '```' + lang + '\n' + globals.preList[num] + '\n```';
|
||||
});
|
5
src/subParsers/makeMarkdown/codeSpan.js
Normal file
5
src/subParsers/makeMarkdown/codeSpan.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
showdown.subParser('makeMarkdown.codeSpan', function (node) {
|
||||
'use strict';
|
||||
|
||||
return '`' + node.innerHTML + '`';
|
||||
});
|
15
src/subParsers/makeMarkdown/emphasis.js
Normal file
15
src/subParsers/makeMarkdown/emphasis.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
showdown.subParser('makeMarkdown.emphasis', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasChildNodes()) {
|
||||
txt += '*';
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
txt += '*';
|
||||
}
|
||||
return txt;
|
||||
});
|
17
src/subParsers/makeMarkdown/header.js
Normal file
17
src/subParsers/makeMarkdown/header.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
showdown.subParser('makeMarkdown.header', function (node, globals, headerLevel) {
|
||||
'use strict';
|
||||
|
||||
var headerMark = new Array(headerLevel + 1).join('#'),
|
||||
txt = '';
|
||||
|
||||
if (node.hasChildNodes()) {
|
||||
txt = headerMark + ' ';
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
}
|
||||
return txt;
|
||||
});
|
5
src/subParsers/makeMarkdown/hr.js
Normal file
5
src/subParsers/makeMarkdown/hr.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
showdown.subParser('makeMarkdown.hr', function () {
|
||||
'use strict';
|
||||
|
||||
return '---';
|
||||
});
|
18
src/subParsers/makeMarkdown/image.js
Normal file
18
src/subParsers/makeMarkdown/image.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
showdown.subParser('makeMarkdown.image', function (node) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasAttribute('src')) {
|
||||
txt += '![' + node.getAttribute('alt') + '](';
|
||||
txt += '<' + node.getAttribute('src') + '>';
|
||||
if (node.hasAttribute('width') && node.hasAttribute('height')) {
|
||||
txt += ' =' + node.getAttribute('width') + 'x' + node.getAttribute('height');
|
||||
}
|
||||
|
||||
if (node.hasAttribute('title')) {
|
||||
txt += ' "' + node.getAttribute('title') + '"';
|
||||
}
|
||||
txt += ')';
|
||||
}
|
||||
return txt;
|
||||
});
|
20
src/subParsers/makeMarkdown/links.js
Normal file
20
src/subParsers/makeMarkdown/links.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
showdown.subParser('makeMarkdown.links', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasChildNodes() && node.hasAttribute('href')) {
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
txt = '[';
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
txt += '](';
|
||||
txt += '<' + node.getAttribute('href') + '>';
|
||||
if (node.hasAttribute('title')) {
|
||||
txt += ' "' + node.getAttribute('title') + '"';
|
||||
}
|
||||
txt += ')';
|
||||
}
|
||||
return txt;
|
||||
});
|
33
src/subParsers/makeMarkdown/list.js
Normal file
33
src/subParsers/makeMarkdown/list.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
showdown.subParser('makeMarkdown.list', function (node, globals, type) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (!node.hasChildNodes()) {
|
||||
return '';
|
||||
}
|
||||
var listItems = node.childNodes,
|
||||
listItemsLenght = listItems.length,
|
||||
listNum = node.getAttribute('start') || 1;
|
||||
|
||||
for (var i = 0; i < listItemsLenght; ++i) {
|
||||
if (typeof listItems[i].tagName === 'undefined' || listItems[i].tagName.toLowerCase() !== 'li') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// define the bullet to use in list
|
||||
var bullet = '';
|
||||
if (type === 'ol') {
|
||||
bullet = listNum.toString() + '. ';
|
||||
} else {
|
||||
bullet = '- ';
|
||||
}
|
||||
|
||||
// parse list item
|
||||
txt += bullet + showdown.subParser('makeMarkdown.listItem')(listItems[i], globals);
|
||||
++listNum;
|
||||
}
|
||||
|
||||
// add comment at the end to prevent consecutive lists to be parsed as one
|
||||
txt += '\n<!-- -->\n';
|
||||
return txt.trim();
|
||||
});
|
25
src/subParsers/makeMarkdown/listItem.js
Normal file
25
src/subParsers/makeMarkdown/listItem.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
showdown.subParser('makeMarkdown.listItem', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var listItemTxt = '';
|
||||
|
||||
var children = node.childNodes,
|
||||
childrenLenght = children.length;
|
||||
|
||||
for (var i = 0; i < childrenLenght; ++i) {
|
||||
listItemTxt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
// if it's only one liner, we need to add a newline at the end
|
||||
if (!/\n$/.test(listItemTxt)) {
|
||||
listItemTxt += '\n';
|
||||
} else {
|
||||
// it's multiparagraph, so we need to indent
|
||||
listItemTxt = listItemTxt
|
||||
.split('\n')
|
||||
.join('\n ')
|
||||
.replace(/^ {4}$/gm, '')
|
||||
.replace(/\n\n+/g, '\n\n');
|
||||
}
|
||||
|
||||
return listItemTxt;
|
||||
});
|
120
src/subParsers/makeMarkdown/node.js
Normal file
120
src/subParsers/makeMarkdown/node.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
|
||||
|
||||
showdown.subParser('makeMarkdown.node', function (node, globals, spansOnly) {
|
||||
'use strict';
|
||||
|
||||
spansOnly = spansOnly || false;
|
||||
|
||||
var txt = '';
|
||||
|
||||
// edge case of text without wrapper paragraph
|
||||
if (node.nodeType === 3) {
|
||||
return showdown.subParser('makeMarkdown.txt')(node, globals);
|
||||
}
|
||||
|
||||
// HTML comment
|
||||
if (node.nodeType === 8) {
|
||||
return '<!--' + node.data + '-->\n\n';
|
||||
}
|
||||
|
||||
// process only node elements
|
||||
if (node.nodeType !== 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var tagName = node.tagName.toLowerCase();
|
||||
|
||||
switch (tagName) {
|
||||
|
||||
//
|
||||
// BLOCKS
|
||||
//
|
||||
case 'h1':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 1) + '\n\n'; }
|
||||
break;
|
||||
case 'h2':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 2) + '\n\n'; }
|
||||
break;
|
||||
case 'h3':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 3) + '\n\n'; }
|
||||
break;
|
||||
case 'h4':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 4) + '\n\n'; }
|
||||
break;
|
||||
case 'h5':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 5) + '\n\n'; }
|
||||
break;
|
||||
case 'h6':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.header')(node, globals, 6) + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.paragraph')(node, globals) + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'blockquote':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.blockquote')(node, globals) + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'hr':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.hr')(node, globals) + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'ol':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.list')(node, globals, 'ol') + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'ul':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.list')(node, globals, 'ul') + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'precode':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.codeBlock')(node, globals) + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'pre':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.pre')(node, globals) + '\n\n'; }
|
||||
break;
|
||||
|
||||
case 'table':
|
||||
if (!spansOnly) { txt = showdown.subParser('makeMarkdown.table')(node, globals) + '\n\n'; }
|
||||
break;
|
||||
|
||||
//
|
||||
// SPANS
|
||||
//
|
||||
case 'code':
|
||||
txt = showdown.subParser('makeMarkdown.codeSpan')(node, globals);
|
||||
break;
|
||||
|
||||
case 'em':
|
||||
case 'i':
|
||||
txt = showdown.subParser('makeMarkdown.emphasis')(node, globals);
|
||||
break;
|
||||
|
||||
case 'strong':
|
||||
case 'b':
|
||||
txt = showdown.subParser('makeMarkdown.strong')(node, globals);
|
||||
break;
|
||||
|
||||
case 'del':
|
||||
txt = showdown.subParser('makeMarkdown.strikethrough')(node, globals);
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
txt = showdown.subParser('makeMarkdown.links')(node, globals);
|
||||
break;
|
||||
|
||||
case 'img':
|
||||
txt = showdown.subParser('makeMarkdown.image')(node, globals);
|
||||
break;
|
||||
|
||||
default:
|
||||
txt = node.outerHTML + '\n\n';
|
||||
}
|
||||
|
||||
// common normalization
|
||||
// TODO eventually
|
||||
|
||||
return txt;
|
||||
});
|
17
src/subParsers/makeMarkdown/paragraph.js
Normal file
17
src/subParsers/makeMarkdown/paragraph.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
showdown.subParser('makeMarkdown.paragraph', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasChildNodes()) {
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
}
|
||||
|
||||
// some text normalization
|
||||
txt = txt.trim();
|
||||
|
||||
return txt;
|
||||
});
|
6
src/subParsers/makeMarkdown/pre.js
Normal file
6
src/subParsers/makeMarkdown/pre.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
showdown.subParser('makeMarkdown.pre', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var num = node.getAttribute('prenum');
|
||||
return '<pre>' + globals.preList[num] + '</pre>';
|
||||
});
|
15
src/subParsers/makeMarkdown/strikethrough.js
Normal file
15
src/subParsers/makeMarkdown/strikethrough.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
showdown.subParser('makeMarkdown.strikethrough', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasChildNodes()) {
|
||||
txt += '~~';
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
txt += '~~';
|
||||
}
|
||||
return txt;
|
||||
});
|
15
src/subParsers/makeMarkdown/strong.js
Normal file
15
src/subParsers/makeMarkdown/strong.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
showdown.subParser('makeMarkdown.strong', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (node.hasChildNodes()) {
|
||||
txt += '**';
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals);
|
||||
}
|
||||
txt += '**';
|
||||
}
|
||||
return txt;
|
||||
});
|
70
src/subParsers/makeMarkdown/table.js
Normal file
70
src/subParsers/makeMarkdown/table.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
showdown.subParser('makeMarkdown.table', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '',
|
||||
tableArray = [[], []],
|
||||
headings = node.querySelectorAll('thead>tr>th'),
|
||||
rows = node.querySelectorAll('tbody>tr'),
|
||||
i, ii;
|
||||
for (i = 0; i < headings.length; ++i) {
|
||||
var headContent = showdown.subParser('makeMarkdown.tableCell')(headings[i], globals),
|
||||
allign = '---';
|
||||
|
||||
if (headings[i].hasAttribute('style')) {
|
||||
var style = headings[i].getAttribute('style').toLowerCase().replace(/\s/g, '');
|
||||
switch (style) {
|
||||
case 'text-align:left;':
|
||||
allign = ':---';
|
||||
break;
|
||||
case 'text-align:right;':
|
||||
allign = '---:';
|
||||
break;
|
||||
case 'text-align:center;':
|
||||
allign = ':---:';
|
||||
break;
|
||||
}
|
||||
}
|
||||
tableArray[0][i] = headContent.trim();
|
||||
tableArray[1][i] = allign;
|
||||
}
|
||||
|
||||
for (i = 0; i < rows.length; ++i) {
|
||||
var r = tableArray.push([]) - 1,
|
||||
cols = rows[i].getElementsByTagName('td');
|
||||
|
||||
for (ii = 0; ii < headings.length; ++ii) {
|
||||
var cellContent = ' ';
|
||||
if (typeof cols[ii] !== 'undefined') {
|
||||
cellContent = showdown.subParser('makeMarkdown.tableCell')(cols[ii], globals);
|
||||
}
|
||||
tableArray[r].push(cellContent);
|
||||
}
|
||||
}
|
||||
|
||||
var cellSpacesCount = 3;
|
||||
for (i = 0; i < tableArray.length; ++i) {
|
||||
for (ii = 0; ii < tableArray[i].length; ++ii) {
|
||||
var strLen = tableArray[i][ii].length;
|
||||
if (strLen > cellSpacesCount) {
|
||||
cellSpacesCount = strLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < tableArray.length; ++i) {
|
||||
for (ii = 0; ii < tableArray[i].length; ++ii) {
|
||||
if (i === 1) {
|
||||
if (tableArray[i][ii].slice(-1) === ':') {
|
||||
tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii].slice(-1), cellSpacesCount - 1, '-') + ':';
|
||||
} else {
|
||||
tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount, '-');
|
||||
}
|
||||
} else {
|
||||
tableArray[i][ii] = showdown.helper.padEnd(tableArray[i][ii], cellSpacesCount);
|
||||
}
|
||||
}
|
||||
txt += '| ' + tableArray[i].join(' | ') + ' |\n';
|
||||
}
|
||||
|
||||
return txt.trim();
|
||||
});
|
15
src/subParsers/makeMarkdown/tableCell.js
Normal file
15
src/subParsers/makeMarkdown/tableCell.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
showdown.subParser('makeMarkdown.tableCell', function (node, globals) {
|
||||
'use strict';
|
||||
|
||||
var txt = '';
|
||||
if (!node.hasChildNodes()) {
|
||||
return '';
|
||||
}
|
||||
var children = node.childNodes,
|
||||
childrenLength = children.length;
|
||||
|
||||
for (var i = 0; i < childrenLength; ++i) {
|
||||
txt += showdown.subParser('makeMarkdown.node')(children[i], globals, true);
|
||||
}
|
||||
return txt.trim();
|
||||
});
|
43
src/subParsers/makeMarkdown/txt.js
Normal file
43
src/subParsers/makeMarkdown/txt.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
showdown.subParser('makeMarkdown.txt', function (node) {
|
||||
'use strict';
|
||||
|
||||
var txt = node.nodeValue;
|
||||
|
||||
// multiple spaces are collapsed
|
||||
txt = txt.replace(/ +/g, ' ');
|
||||
|
||||
// replace the custom ¨NBSP; with a space
|
||||
txt = txt.replace(/¨NBSP;/g, ' ');
|
||||
|
||||
// ", <, > and & should replace escaped html entities
|
||||
txt = showdown.helper.unescapeHTMLEntities(txt);
|
||||
|
||||
// escape markdown magic characters
|
||||
// emphasis, strong and strikethrough - can appear everywhere
|
||||
// we also escape pipe (|) because of tables
|
||||
// and escape ` because of code blocks and spans
|
||||
txt = txt.replace(/([*_~|`])/g, '\\$1');
|
||||
|
||||
// escape > because of blockquotes
|
||||
txt = txt.replace(/^(\s*)>/g, '\\$1>');
|
||||
|
||||
// hash character, only troublesome at the beginning of a line because of headers
|
||||
txt = txt.replace(/^#/gm, '\\#');
|
||||
|
||||
// horizontal rules
|
||||
txt = txt.replace(/^(\s*)([-=]{3,})(\s*)$/, '$1\\$2$3');
|
||||
|
||||
// dot, because of ordered lists, only troublesome at the beginning of a line when preceded by an integer
|
||||
txt = txt.replace(/^( {0,3}\d+)\./gm, '$1\\.');
|
||||
|
||||
// +, * and -, at the beginning of a line becomes a list, so we need to escape them also (asterisk was already escaped)
|
||||
txt = txt.replace(/^( {0,3})([+-])/gm, '$1\\$2');
|
||||
|
||||
// images and links, ] followed by ( is problematic, so we escape it
|
||||
txt = txt.replace(/]([\s]*)\(/g, '\\]$1\\(');
|
||||
|
||||
// reference URIs must also be escaped
|
||||
txt = txt.replace(/^ {0,3}\[([\S \t]*?)]:/gm, '\\[$1]:');
|
||||
|
||||
return txt;
|
||||
});
|
31
test/bootstrap.js
vendored
31
test/bootstrap.js
vendored
|
@ -9,6 +9,8 @@
|
|||
require('source-map-support').install();
|
||||
require('chai').should();
|
||||
var fs = require('fs');
|
||||
var jsdom = require('jsdom');
|
||||
var document = new jsdom.JSDOM('', {}).window.document; // jshint ignore:line
|
||||
|
||||
function getTestSuite (dir) {
|
||||
return fs.readdirSync(dir)
|
||||
|
@ -16,6 +18,12 @@
|
|||
.map(map(dir));
|
||||
}
|
||||
|
||||
function getHtmlToMdTestSuite (dir) {
|
||||
return fs.readdirSync(dir)
|
||||
.filter(filter())
|
||||
.map(map2(dir));
|
||||
}
|
||||
|
||||
function filter () {
|
||||
return function (file) {
|
||||
var ext = file.slice(-3);
|
||||
|
@ -39,9 +47,27 @@
|
|||
};
|
||||
}
|
||||
|
||||
function assertion (testCase, converter) {
|
||||
function map2 (dir) {
|
||||
return function (file) {
|
||||
var name = file.replace('.md', ''),
|
||||
htmlPath = dir + name + '.html',
|
||||
html = fs.readFileSync(htmlPath, 'utf8'),
|
||||
mdPath = dir + name + '.md',
|
||||
md = fs.readFileSync(mdPath, 'utf8');
|
||||
|
||||
return {
|
||||
name: name,
|
||||
input: html,
|
||||
expected: md
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function assertion (testCase, converter, type) {
|
||||
return function () {
|
||||
testCase.actual = converter.makeHtml(testCase.input);
|
||||
//var conv = (type === 'makeMd') ? converter.makeMd : converter.makeHtml;
|
||||
|
||||
testCase.actual = (type === 'makeMd') ? converter.makeMd(testCase.input, document) : converter.makeHtml(testCase.input);
|
||||
testCase = normalize(testCase);
|
||||
|
||||
// Compare
|
||||
|
@ -81,6 +107,7 @@
|
|||
|
||||
module.exports = {
|
||||
getTestSuite: getTestSuite,
|
||||
getHtmlToMdTestSuite: getHtmlToMdTestSuite,
|
||||
assertion: assertion,
|
||||
normalize: normalize,
|
||||
showdown: require('../.build/showdown.js')
|
||||
|
|
4
test/makeMd/blockquote.html
Normal file
4
test/makeMd/blockquote.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<blockquote>some
|
||||
multiline
|
||||
blockquote
|
||||
</blockquote>
|
1
test/makeMd/blockquote.md
Normal file
1
test/makeMd/blockquote.md
Normal file
|
@ -0,0 +1 @@
|
|||
> some multiline blockquote
|
8
test/makeMd/codeblock.html
Normal file
8
test/makeMd/codeblock.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<pre><code>some code
|
||||
</code></pre>
|
||||
|
||||
<pre><code data-language="javascript">
|
||||
function foo() {
|
||||
return 'bar';
|
||||
}
|
||||
</code></pre>
|
9
test/makeMd/codeblock.md
Normal file
9
test/makeMd/codeblock.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
```
|
||||
some code
|
||||
```
|
||||
|
||||
```javascript
|
||||
function foo() {
|
||||
return 'bar';
|
||||
}
|
||||
```
|
1
test/makeMd/codespan.html
Normal file
1
test/makeMd/codespan.html
Normal file
|
@ -0,0 +1 @@
|
|||
some <code>code</code> span
|
1
test/makeMd/codespan.md
Normal file
1
test/makeMd/codespan.md
Normal file
|
@ -0,0 +1 @@
|
|||
some `code` span
|
1
test/makeMd/emphasis.html
Normal file
1
test/makeMd/emphasis.html
Normal file
|
@ -0,0 +1 @@
|
|||
<em>foobar</em>
|
1
test/makeMd/emphasis.md
Normal file
1
test/makeMd/emphasis.md
Normal file
|
@ -0,0 +1 @@
|
|||
*foobar*
|
6
test/makeMd/heading.html
Normal file
6
test/makeMd/heading.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<h1>foo h1</h1>
|
||||
<h2>foo h2</h2>
|
||||
<h3>foo h3</h3>
|
||||
<h4>foo h4</h4>
|
||||
<h5>foo h5</h5>
|
||||
<h6>foo h6</h6>
|
11
test/makeMd/heading.md
Normal file
11
test/makeMd/heading.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# foo h1
|
||||
|
||||
## foo h2
|
||||
|
||||
### foo h3
|
||||
|
||||
#### foo h4
|
||||
|
||||
##### foo h5
|
||||
|
||||
###### foo h6
|
3
test/makeMd/hr.html
Normal file
3
test/makeMd/hr.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<hr />
|
||||
|
||||
<hr>
|
3
test/makeMd/hr.md
Normal file
3
test/makeMd/hr.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
|
||||
---
|
1
test/makeMd/image.html
Normal file
1
test/makeMd/image.html
Normal file
|
@ -0,0 +1 @@
|
|||
<img src="img.png" alt="an image" width="200px" height="300px" title="a title"/>
|
1
test/makeMd/image.md
Normal file
1
test/makeMd/image.md
Normal file
|
@ -0,0 +1 @@
|
|||
![an image](<img.png> =200pxx300px "a title")
|
1
test/makeMd/link.html
Normal file
1
test/makeMd/link.html
Normal file
|
@ -0,0 +1 @@
|
|||
<a href="/url" title="a title">some link</a>
|
1
test/makeMd/link.md
Normal file
1
test/makeMd/link.md
Normal file
|
@ -0,0 +1 @@
|
|||
[some link](</url> "a title")
|
15
test/makeMd/list.html
Normal file
15
test/makeMd/list.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<ul>
|
||||
<li>foo</li>
|
||||
<li>bar</li>
|
||||
<li>baz</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><p>foo</p></li>
|
||||
<li><p>bar</p></li>
|
||||
<li>baz</li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li>one</li>
|
||||
<li>2</li>
|
||||
<li>three</li>
|
||||
</ol>
|
19
test/makeMd/list.md
Normal file
19
test/makeMd/list.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
- foo
|
||||
- bar
|
||||
- baz
|
||||
|
||||
<!-- -->
|
||||
|
||||
- foo
|
||||
|
||||
- bar
|
||||
|
||||
- baz
|
||||
|
||||
<!-- -->
|
||||
|
||||
1. one
|
||||
2. 2
|
||||
3. three
|
||||
|
||||
<!-- -->
|
5
test/makeMd/other-html.html
Normal file
5
test/makeMd/other-html.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<div>this is a div</div>
|
||||
<label for="ipt">a label</label>
|
||||
<input id="ipt" type="text">
|
||||
<iframe></iframe>
|
||||
<textarea>some textarea</textarea>
|
9
test/makeMd/other-html.md
Normal file
9
test/makeMd/other-html.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div>this is a div</div>
|
||||
|
||||
<label for="ipt">a label</label>
|
||||
|
||||
<input id="ipt" type="text">
|
||||
|
||||
<iframe></iframe>
|
||||
|
||||
<textarea>some textarea</textarea>
|
3
test/makeMd/paragraph.html
Normal file
3
test/makeMd/paragraph.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<p>a paragraph
|
||||
of multi-line
|
||||
text</p>
|
1
test/makeMd/paragraph.md
Normal file
1
test/makeMd/paragraph.md
Normal file
|
@ -0,0 +1 @@
|
|||
a paragraph of multi-line text
|
1
test/makeMd/strikethrough.html
Normal file
1
test/makeMd/strikethrough.html
Normal file
|
@ -0,0 +1 @@
|
|||
<del>deleted text</del>
|
1
test/makeMd/strikethrough.md
Normal file
1
test/makeMd/strikethrough.md
Normal file
|
@ -0,0 +1 @@
|
|||
~~deleted text~~
|
1
test/makeMd/strong.html
Normal file
1
test/makeMd/strong.html
Normal file
|
@ -0,0 +1 @@
|
|||
<strong>strong text</strong>
|
1
test/makeMd/strong.md
Normal file
1
test/makeMd/strong.md
Normal file
|
@ -0,0 +1 @@
|
|||
**strong text**
|
21
test/makeMd/table.html
Normal file
21
test/makeMd/table.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>head 1</th>
|
||||
<th>head 2</th>
|
||||
<th>head 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>foo</td>
|
||||
<td><em>bar</em></td>
|
||||
<td>baz</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><del>a</del></td>
|
||||
<td><strong>b</strong></td>
|
||||
<td><code>cccc</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
4
test/makeMd/table.md
Normal file
4
test/makeMd/table.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
| head 1 | head 2 | head 3 |
|
||||
| ------ | ------ | ------ |
|
||||
| foo | *bar* | baz |
|
||||
| ~~a~~ | **b** | `cccc` |
|
|
@ -27,6 +27,27 @@ describe('showdown.Converter', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Converter.options extensions', function () {
|
||||
var runCount;
|
||||
showdown.extension('testext', function () {
|
||||
return [{
|
||||
type: 'output',
|
||||
filter: function (text) {
|
||||
runCount = runCount + 1;
|
||||
return text;
|
||||
}
|
||||
}];
|
||||
});
|
||||
|
||||
var converter = new showdown.Converter({extensions: ['testext']});
|
||||
|
||||
it('output extensions should run once', function () {
|
||||
runCount = 0;
|
||||
converter.makeHtml('# testext');
|
||||
runCount.should.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata methods', function () {
|
||||
var converter = new showdown.Converter();
|
||||
|
||||
|
|
|
@ -10,27 +10,6 @@ describe('showdown.Converter', function () {
|
|||
|
||||
var showdown = require('../bootstrap').showdown;
|
||||
|
||||
describe('Converter.options extensions', function () {
|
||||
var runCount;
|
||||
showdown.extension('testext', function () {
|
||||
return [{
|
||||
type: 'output',
|
||||
filter: function (text) {
|
||||
runCount = runCount + 1;
|
||||
return text;
|
||||
}
|
||||
}];
|
||||
});
|
||||
|
||||
var converter = new showdown.Converter({extensions: ['testext']});
|
||||
|
||||
it('output extensions should run once', function () {
|
||||
runCount = 0;
|
||||
converter.makeHtml('# testext');
|
||||
runCount.should.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeHtml() with option omitExtraWLInCodeBlocks', function () {
|
||||
var converter = new showdown.Converter({omitExtraWLInCodeBlocks: true}),
|
||||
text = 'var foo = bar;',
|
||||
|
|
25
test/node/showdown.Converter.makeMarkdown.js
Normal file
25
test/node/showdown.Converter.makeMarkdown.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Created by Estevao on 15-01-2015.
|
||||
*/
|
||||
|
||||
describe('showdown.Converter', function () {
|
||||
'use strict';
|
||||
|
||||
require('source-map-support').install();
|
||||
require('chai').should();
|
||||
var jsdom = require('jsdom');
|
||||
var document = new jsdom.JSDOM('', {}).window.document; // jshint ignore:line
|
||||
var showdown = require('../bootstrap').showdown;
|
||||
|
||||
describe('makeMarkdown()', function () {
|
||||
var converter = new showdown.Converter();
|
||||
|
||||
it('should parse a simple html string', function () {
|
||||
var html = '<a href="/somefoo.html">a link</a>\n';
|
||||
var md = '[a link](</somefoo.html>)';
|
||||
|
||||
converter.makeMd(html, document).should.equal(md);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
12
test/node/testsuite.makemd.js
Normal file
12
test/node/testsuite.makemd.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var bootstrap = require('../bootstrap.js'),
|
||||
converter = new bootstrap.showdown.Converter(),
|
||||
testsuite = bootstrap.getHtmlToMdTestSuite('test/makeMd/'),
|
||||
assertion = bootstrap.assertion;
|
||||
|
||||
describe('makeMd() standard testsuite', function () {
|
||||
'use strict';
|
||||
for (var i = 0; i < testsuite.length; ++i) {
|
||||
it(testsuite[i].name.replace(/-/g, ' '), assertion(testsuite[i], converter, 'makeMd'));
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user