Your code isn't that much re-usable. I can't include it in a file and call it since I could easily break it from anywhere.
What I suggest is that you wrap it in the following block:
(function(window, undefined){
'use strict';
[code]
})(Function('return this')());
This will protect your code in so many ways:
- You guarantee that the
window
object is the window
- You guarantee that
undefined
is really undefined
- You can be 100% sure that there won't be variables on the global scope
- All variables are local (looks the same, but it is a tiny bit different)
- The possibility of a script screwing up your code is
null
.
And this takes what? 1 minute? And you have to change almost nothing!
You will notice the 'use strict;'
there. You may be alienated by it.
This has been re-re-re-re-re-re-itterated here. Using it adds some security features and prevent some 'stupid' bloopers and other mistakes and distractions.
You can read about it in greater detail on MDN's page.
You currently are writting everything into the markdown
variable. Before going any further, let me take the liberty to teach you about the amazing return
statement:
When a return
statement is called in a function, the execution of this function is stopped. If specified, a given value is returned to the function caller. If the expression is omitted, undefined
is returned instead.
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
Inside your convertToMarkdown()
function, instead of cramming it all into that variable, use a return
.
Inside the same function, you have the following:
if (tag == undefined) { //for text nodes
markdown += curEl.textContent;
} else {
Which isn't fine. It is too complex to explain in less than 20k characters, but that piece must be gone! I'll show how later on.
One of the problems with this is also that you are using .textContent
. That isn't much compatible with other "browsers" (*cough*
IE *cough*
). The most compatible method is by using .innerText
. But Firefox now decided that it wouldn't support it. Fear not, just add this line at the top:
var TEXT_PROPERTY = document.head.innerText ? 'innerText' : 'textContent';
And use it like curEl[TEXT_PROPERTY]
to get the text content.
Instead of re-creating a parser object, you can simply create a <div>
, keep it in memory, and use it to extract the elements. Setting the .innerHTML
of that element will have the same effect than the parser, but faster. You then have access to tons of tools readily available on DOM elements.
Your choice of newlines is quite weird. Browsers will all the time convert them to \r\n
. You can use it, since you know what to expect. Imagine what would be to write \n
and get \r\n
instead. That would drive some guys insane!
On your conversions
object, you have methods that have 1 argument, but use it for nothing. You could just drop that useless argument. Done.
And now, my promised alternative:
(function(window, undefined){
'use strict';
var TEXT_PROPERTY = document.head.innerText ? 'innerText' : 'textContent';
var NEWLINE = '\r\n';
var indent = function(text){
return text.replace(/(\r\n|\r|\n)/g,'$1 ');
};
var conversions = {
BR: function(){
return ' ' + NEWLINE;
},
P: function(elem) {
return NEWLINE + NEWLINE + elem.innerHTML + NEWLINE;
},
H1: function(elem) {
return '# ' + elem.innerHTML + NEWLINE;
},
H2: function(elem) {
return '## ' + elem.innerHTML + NEWLINE;
},
H3: function(elem) {
return '### ' + elem.innerHTML + NEWLINE;
},
HR: function() {
//we use 4 to do not cause confusion with <strike></strike>
return '----' + NEWLINE;
},
BLOCKQUOTE: function(elem) {
return '> ' + elem
.innerHTML
.replace(
/(\r\n|\r|\n)/g,
'$1> '
);
},
IMG: function(elem) {
return '';
},
A: function(elem) {
return '[' + elem.innerHTML + '](' + elem.href + ')';
},
UL: function(elem) {
var li = elem.children;
var length = li.length;
var md = '';
for (var i = 0; i < length; i++) {
md += ' - ' + indent(li[i].innerHTML) + NEWLINE;
}
return md;
},
OL: function(elem) {
var li = elem.children;
var length = li.length;
var md = '';
var start = elem.start|0;
for (var i = 0; i < length; i++) {
md += (i + start) + '. ' + indent(li[i].innerHTML) + NEWLINE;
}
return md;
},
B: function(elem) {
return '**' + elem.innerHTML + '**';
},
STRONG: function(elem) {
return this.B(elem);
},
STRIKE: function(elem) {
return '---' + elem.innerHTML + '---';
},
DEL: function(elem) {
return this.STRIKE(elem);
},
I: function(elem) {
return '*' + elem.innerHTML + '*';
},
PRE: function(elem){
return indent(this.innerHTML) + NEWLINE;
},
CODE: function(elem){
if(elem.parentNode.tagName != 'PRE')
{
return '``' + elem.innerHTML.replace(/\r|\n/g,'').replace(/^\s*(.*)\s*$/,'$1') + '``';
}
else
{
this.PRE(elem);
}
}
};
var toMarkdown = function(html){
var DIV = document.createElement('div'); //will have the HTML to parse.
DIV.innerHTML = html + '';
for(var tag in conversions)
{
var elements = Array.prototype.slice.call(DIV.getElementsByTagName(tag.toLowerCase()));
var length = elements.length;
for(var i = 0; i < length; i++)
{
var element = elements[i];
if(element.childNodes.length > 1)
{
element.innerHTML = toMarkdown(element.innerHTML);
}
element.parentNode.replaceChild(
document.createTextNode(conversions[tag](element)),
element
);
}
}
return DIV.innerHTML;
};
window.MarkdownConverter = {
addParser: function(tag, fn){
if( !this.hasParser(tag) )
{
conversions[(tag + '').toUpperCase()] = fn;
return true;
}
return false;
},
hasParser: function(tag){
tag = (tag + '').toUpperCase();
return (tag in conversions);
},
fromHTML: function(html){
return toMarkdown(html + '');
}
};
})(Function('return this')());
It exposes a very basic API. The idea is to be as close as I can from the reference.
You can read about it here: https://stackoverflow.com/editing-help
Some mistakes on matching the were fixed. Also, the original version was limited to 1 level only. Which means that things like <p>A <b>bold</b></p>
wouldn't produce the right markdown. To fix that, I've added recursion. Also, the <blockquote>
only had the markdown on the first line. All those issues and many others (that I don't remember) were fixed.
Another addition was the support for the start
attribute (it was deprecated on HTML4.01, but it isn't on HTML5) on ordened lists.
Example of the markdown:
(function(window, undefined){
'use strict';
var TEXT_PROPERTY = document.head.innerText ? 'innerText' : 'textContent';
var NEWLINE = '\r\n';
var indent = function(text){
return text.replace(/(\r\n|\r|\n)/g,'$1 ');
};
var conversions = {
BR: function(){
return ' ' + NEWLINE;
},
P: function(elem) {
return NEWLINE + NEWLINE + elem.innerHTML + NEWLINE;
},
H1: function(elem) {
return '# ' + elem.innerHTML + NEWLINE;
},
H2: function(elem) {
return '## ' + elem.innerHTML + NEWLINE;
},
H3: function(elem) {
return '### ' + elem.innerHTML + NEWLINE;
},
HR: function() {
//we use 4 to do not cause confusion with <strike></strike>
return '----' + NEWLINE;
},
BLOCKQUOTE: function(elem) {
return '> ' + elem
.innerHTML
.replace(
/(\r\n|\r|\n)/g,
'$1> '
);
},
IMG: function(elem) {
return '';
},
A: function(elem) {
return '[' + elem.innerHTML + '](' + elem.href + ')';
},
UL: function(elem) {
var li = elem.children;
var length = li.length;
var md = '';
for (var i = 0; i < length; i++) {
md += ' - ' + indent(li[i].innerHTML) + NEWLINE;
}
return md;
},
OL: function(elem) {
var li = elem.children;
var length = li.length;
var md = '';
var start = elem.start|0;
for (var i = 0; i < length; i++) {
md += (i + start) + '. ' + indent(li[i].innerHTML) + NEWLINE;
}
return md;
},
B: function(elem) {
return '**' + elem.innerHTML + '**';
},
STRONG: function(elem) {
return this.B(elem);
},
STRIKE: function(elem) {
return '---' + elem.innerHTML + '---';
},
DEL: function(elem) {
return this.STRIKE(elem);
},
I: function(elem) {
return '*' + elem.innerHTML + '*';
},
PRE: function(elem){
return indent(this.innerHTML) + NEWLINE;
},
CODE: function(elem){
if(elem.parentNode.tagName != 'PRE')
{
return '``' + elem.innerHTML.replace(/\r|\n/g,'').replace(/^\s*(.*)\s*$/,'$1') + '``';
}
else
{
this.PRE(elem);
}
}
};
var toMarkdown = function(html){
var DIV = document.createElement('div'); //will have the HTML to parse.
DIV.innerHTML = html + '';
for(var tag in conversions)
{
var elements = Array.prototype.slice.call(DIV.getElementsByTagName(tag.toLowerCase()));
var length = elements.length;
for(var i = 0; i < length; i++)
{
var element = elements[i];
if(element.childNodes.length > 1)
{
element.innerHTML = toMarkdown(element.innerHTML);
}
element.parentNode.replaceChild(
document.createTextNode(conversions[tag](element)),
element
);
}
}
return DIV.innerHTML;
};
window.MarkdownConverter = {
addParser: function(tag, fn){
if( !this.hasParser(tag) )
{
conversions[(tag + '').toUpperCase()] = fn;
return true;
}
return false;
},
hasParser: function(tag){
tag = (tag + '').toUpperCase();
return (tag in conversions);
},
fromHTML: function(html){
return toMarkdown(html + '');
}
};
})(Function('return this')());
document.body.innerHTML=
'<pre>' +
MarkdownConverter
.fromHTML(
document
.getElementById('html')
.innerHTML
)
.replace(/</g,'<')
.replace(/>/g,'>') +
'</pre>';
<div id="html">
<p>Te<i><b>s</b></i>t</p>
<h1>h1</h1>
<br>
<h2>h2</h2>
<br>
<h3>h3</h3>
<br>text outside everything
<br>
<h2>(and another element!)</h2>
<br>
<img src='http://example.com/example.png'>
<br><a href='http://google.com'>a link!</a>
<br>
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
<br>
<ol start="2">
<li>item 1</li>
<li>item 2</li>
<li>item <b>3</b></li>
</ol>
<br><strong>BOLD TEXT</strong> and <i>ITALICISED TEXT</i>
<br>
<blockquote>blockquote</blockquote>
<br>
</div>
<ol>
starting at values other than 1. Also,<ul>
tags have a space before the-
\$\endgroup\$1
, when the starting number can be, for example, 10. \$\endgroup\$