refactor HTML module; add functionality to Ox.parseMarkdown; add tests

This commit is contained in:
rolux 2012-05-31 00:47:19 +02:00
parent d6b86b518b
commit 3683bf3d29

View file

@ -26,67 +26,50 @@
].join('|') + ')\\/?>', 'gi') ].join('|') + ')\\/?>', 'gi')
}, },
replace = { replace = {
mail: [ a: [
/\b([0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6})\b/gi, '$1' [
/* /<a [^<>]*?href="((\/|https?:\/\/|mailto:).+?)".*?>/gi,
function(match, mail) { '<a href="{1}">',
return Ox.encodeEmailAddress(mail);
}
*/
],
namedEntity: [
new RegExp('(' + Ox.values(htmlEntities).join('|') + ')', 'g'),
function(match) {
return Ox.keyOf(htmlEntities, match);
}
],
numericEntity: [
/&#([0-9A-FX]+);/gi,
function(match, code) {
return Ox.char(
/^X/i.test(code)
? parseInt(code.slice(1), 16)
: parseInt(code, 10)
);
}
],
tag: {
a: [
[
/<a [^<>]*?href="((\/|https?:\/\/|mailto:).+?)".*?>/gi,
'<a href="{1}">',
],
[
/<\/a>/gi,
'</a>'
]
], ],
img: [ [
[ /<\/a>/gi,
/<img [^<>]*?src="((\/|https?:\/\/).+?)".*?>/gi, '</a>'
'<img src="$1">' ]
] ],
img: [
[
/<img [^<>]*?src="((\/|https?:\/\/).+?)".*?>/gi,
'<img src="$1">'
]
],
rtl: [
[
/<rtl>/gi,
'<div style="direction: rtl">'
], ],
rtl: [ [
/<\/rtl>/gi,
'</div>'
]
],
'*': function(tag) {
return [
[ [
/<rtl>/gi, new RegExp('</?' + tag + ' ?/?>', 'gi'),
'<div style="direction: rtl">' '{0}'
],
[
/<\/rtl>/gi,
'</div>'
] ]
], ];
'*': function(tag) { }
return [ },
[ salt = Ox.range(2).map(function(){
new RegExp('</?' + tag + ' ?/?>', 'gi'), return Ox.range(16).map(function() {
'{0}' return Ox.char(65 + Ox.random(26));
] }).join('');
]; });
}
}, function addLinks(string, obfuscate) {
url: [ return string
.replace(
/\b((https?:\/\/|www\.).+?)([\.,:;!\?\)\]]*?(\s|$))/gi, /\b((https?:\/\/|www\.).+?)([\.,:;!\?\)\]]*?(\s|$))/gi,
function(match, url, prefix, end) { function(match, url, prefix, end) {
prefix = prefix.toLowerCase() == 'www.' ? 'http://' : ''; prefix = prefix.toLowerCase() == 'www.' ? 'http://' : '';
@ -95,13 +78,34 @@
{end: end, prefix: prefix, url: url} {end: end, prefix: prefix, url: url}
); );
} }
] )
}, .replace(
salt = Ox.range(2).map(function(){ /\b([0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6})\b/gi,
return Ox.range(16).map(function() { obfuscate ? function(match, mail) {
return Ox.char(65 + Ox.random(26)); return Ox.encodeEmailAddress(mail);
}).join(''); } : '<a href="mailto:$1">$1</a>'
}); );
}
function decodeHTMLEntities(string) {
return string
.replace(
new RegExp('(' + Ox.values(htmlEntities).join('|') + ')', 'g'),
function(match) {
return Ox.keyOf(htmlEntities, match);
}
)
.replace(
/&#([0-9A-FX]+);/gi,
function(match, code) {
return Ox.char(
/^X/i.test(code)
? parseInt(code.slice(1), 16)
: parseInt(code, 10)
);
}
);
}
// Splits a string into text (even indices) and tags (odd indices), ignoring // Splits a string into text (even indices) and tags (odd indices), ignoring
// tags with starting positions that are included in the ignore array // tags with starting positions that are included in the ignore array
@ -136,11 +140,6 @@
@*/ @*/
Ox.addLinks = function(string, isHTML) { Ox.addLinks = function(string, isHTML) {
var isLink = false; var isLink = false;
function replaceString(string) {
return string
.replace(replace.mail[0], replace.mail[1])
.replace(replace.url[0], replace.url[1]);
}
return isHTML return isHTML
? splitHTMLTags(string).map(function(string, i) { ? splitHTMLTags(string).map(function(string, i) {
var isTag = i % 2; var isTag = i % 2;
@ -151,9 +150,9 @@
isLink = false; isLink = false;
} }
} }
return isTag || isLink ? string : replaceString(string); return isTag || isLink ? string : addLinks(string);
}).join('') }).join('')
: Ox.normalizeHTML(replaceString(string)); : Ox.normalizeHTML(addLinks(string));
}; };
/*@ /*@
@ -224,9 +223,7 @@
Ox.decodeHTMLEntities = function(string, decodeAll) { Ox.decodeHTMLEntities = function(string, decodeAll) {
return decodeAll return decodeAll
? Ox.decodeHTMLEntities(Ox.normalizeHTML(string)) ? Ox.decodeHTMLEntities(Ox.normalizeHTML(string))
: String(string) : decodeHTMLEntities(string);
.replace(replace.namedEntity[0], replace.namedEntity[1])
.replace(replace.numericEntity[0], replace.numericEntity[1]);
}; };
/*@ /*@
@ -360,10 +357,20 @@
[example](http://example.com "example.com") -> <a href="http://example.com" title="example.com">example</a> [example](http://example.com "example.com") -> <a href="http://example.com" title="example.com">example</a>
> Ox.parseMarkdown('*foo* **bar** `baz` ``back`tick``') > Ox.parseMarkdown('*foo* **bar** `baz` ``back`tick``')
'<em>foo</em> <strong>bar</strong> <code>baz</code> <code>back`tick</code>' '<em>foo</em> <strong>bar</strong> <code>baz</code> <code>back`tick</code>'
> Ox.parseMarkdown('<http://example.com>')
'<a href="http://example.com">http://example.com</a>'
> Ox.parseMarkdown('[example](http://example.com "example.com")') > Ox.parseMarkdown('[example](http://example.com "example.com")')
'<a href="http://example.com" title="example.com">example</a>' '<a href="http://example.com" title="example.com">example</a>'
> Ox.parseMarkdown('[example](http://example.com?foo=bar&bar=baz)') > Ox.parseMarkdown('[example](http://example.com?foo=bar&bar=baz)')
'<a href="http://example.com?foo=bar&amp;bar=baz">example</a>' '<a href="http://example.com?foo=bar&amp;bar=baz">example</a>'
> Ox(Ox.parseMarkdown('<mail@example.com>')).startsWith('<a href="')
true
> Ox(Ox.parseMarkdown('<mail@example.com>')).endsWith('</a>')
true
> Ox(Ox.parseMarkdown('<mail@example.com>')).count(':')
1
> Ox(Ox.parseMarkdown('<mail@example.com>')).decodeHTMLEntities()
'<a href="mailto:mail@example.com">mail@example.com</a>'
*/ */
Ox.parseMarkdown = function(string) { Ox.parseMarkdown = function(string) {
// see https://github.com/coreyti/showdown/blob/master/src/showdown.js // see https://github.com/coreyti/showdown/blob/master/src/showdown.js
@ -379,24 +386,35 @@
) )
.replace( .replace(
/\n```(.*)\n([^`]+)\n```/g, /\n```(.*)\n([^`]+)\n```/g,
function(match, a, b) { function(match, classname, code) {
return '<pre><code' + (a ? ' class="' + a + '"' : '') + '>' return '<pre><code'
+ b + '\n</code></pre>'; + (classname ? ' class="' + classname + '"' : '')
+ '>' + code + '\n</code></pre>';
} }
) )
.replace( .replace(
/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
function(match, a, b, c, d) { function(match, prev, backticks, code, next) {
return a + '<code>' return prev + '<code>'
+ Ox.encodeHTMLEntities(c.trim()) + '</code>'; + Ox.encodeHTMLEntities(code.trim()) + '</code>';
} }
) )
.replace( .replace(
/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, /(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
function(match, a, b, c, d, e, f, g) { function(match, all, text, id, url, rest, quote, title) {
return '<a href="' + Ox.encodeHTMLEntities(d) + '"' + ( return '<a href="' + Ox.encodeHTMLEntities(url) + '"' + (
g ? ' title="' + Ox.encodeHTMLEntities(g) + '"' : '' title ? ' title="' + Ox.encodeHTMLEntities(title) + '"' : ''
) + '>' + b + '</a>'; ) + '>' + text + '</a>';
}
)
.replace(
/<((https?|ftp|dict):[^'">\s]+)>/gi,
'<a href=\"$1\">$1</a>'
)
.replace(
/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
function(match, mail) {
return Ox.encodeEmailAddress(mail);
} }
); );
}; };
@ -447,7 +465,7 @@
}); });
} }
tags.forEach(function(tag) { tags.forEach(function(tag) {
var array = replace.tag[tag] || replace.tag['*'](tag); var array = replace[tag] || replace['*'](tag);
Ox.forEach(array, function(value) { Ox.forEach(array, function(value) {
html = html.replace(value[0], function() { html = html.replace(value[0], function() {
matches.push(Ox.formatString(value[1], arguments)); matches.push(Ox.formatString(value[1], arguments));