mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
291 lines
8.1 KiB
JavaScript
291 lines
8.1 KiB
JavaScript
(function(){
|
|
|
|
$.normalize = function(html, customOpt){
|
|
html = html || '';
|
|
var root$, wrapped, opt;
|
|
opt = html.opt || (customOpt ? prepareOptTags($.extend(true, baseOpt, customOpt))
|
|
: defaultOpt);
|
|
if(!html.opt){
|
|
// first call
|
|
unstableList.length = 0; // drop state from previous run (in case there has been error)
|
|
root$ = $('<div>'+html+'</div>');
|
|
}
|
|
// initial recursion
|
|
(html.$ || root$).contents().each(function(){
|
|
if(this.nodeType === this.TEXT_NODE) {
|
|
this.textContent = this.textContent.replace(/^[ \n]+|[ \n]+$/g, ' ');
|
|
return;
|
|
}
|
|
var a = {$: $(this), opt: opt};
|
|
initTag(a);
|
|
$.normalize(a);
|
|
});
|
|
if(root$){
|
|
stateMachine();
|
|
return root$.html();
|
|
}
|
|
}
|
|
|
|
var baseOpt = {
|
|
hierarchy: ['div', 'pre', 'ol', 'ul', 'li',
|
|
'h1', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', // block
|
|
'b', 'code', 'i', 'span', 's', 'sub', 'sup', 'u', // inline
|
|
'br', 'img'] // empty
|
|
,tags: {
|
|
'a': {attrs:{'href':1}, exclude:{'a':1}},
|
|
'b': {exclude:{'b':1,'p':1}},
|
|
'br': {empty: 1},
|
|
'i': {exclude:{'i':1,'p':1}},
|
|
'img': {attrs:{'src':1}, empty: 1},
|
|
'span': {exclude:{'p':1,'ul':1,'ol':1,'li':1,'br':1}},
|
|
's': {space:1},
|
|
'u': {exclude:{'u':1,'p':1},space:1},
|
|
}
|
|
,convert: {
|
|
'em': 'i', 'strong': 'b', 'strike': 's',
|
|
}
|
|
,attrs: {
|
|
'id':1
|
|
,'class':1
|
|
,'style':1
|
|
}
|
|
,blockTag: function(a){
|
|
return a.opt.tags[a.tag].order < a.opt.tags.a.order;
|
|
}
|
|
,mutate: [exclude, moveSpaceUp, next, parentOrderWrap]
|
|
}
|
|
|
|
var defaultOpt = prepareOptTags($.extend(true, {}, baseOpt));
|
|
|
|
var unstableList = [];
|
|
|
|
function addUnstable(a) { // NOT ES5
|
|
if(!a.tag) { throw Error("not tag in ", a) }
|
|
if(a.unstable) return;
|
|
unstableList.push(a);
|
|
a.unstable = true;
|
|
}
|
|
|
|
function initTag(a) {
|
|
// initial handling (container, convert, attributes):
|
|
a.tag = tag(a.$);
|
|
if(empty(a)) {
|
|
return;
|
|
}
|
|
parseAndRemoveAttrs(a);
|
|
convert(a);
|
|
setAttrs(a);
|
|
a.$[0].a = a; // link from dom element back to a
|
|
// state machine init
|
|
unstableList.push(a);
|
|
a.unstable = true;
|
|
return a;
|
|
}
|
|
|
|
function stateMachine() {
|
|
if(unstableList.length===0)
|
|
return;
|
|
var a, i = -1;
|
|
while (a = unstableList.pop()) { // PERF: running index is probably faster than shift (mutates array)
|
|
a.unstable = false;
|
|
$(a.opt.mutate).each(function(i,fn){
|
|
return fn && fn(a, addUnstable);
|
|
});
|
|
}
|
|
}
|
|
|
|
function prepareOptTags(opt) {
|
|
var name, tag, tags = opt.tags;
|
|
for(name in tags) {
|
|
if(opt.hierarchy.indexOf(name)===-1)
|
|
throw Error('tag "'+name+'" is missing hierachy definition');
|
|
}
|
|
opt.hierarchy.forEach(function(name){
|
|
if(!tags[name]){
|
|
tags[name] = {attrs: opt.attrs};
|
|
}
|
|
(tag=tags[name]).attrs = $.extend(tag.attrs||{}, opt.attrs);
|
|
tag.name = name; // not used, debug help (REMOVE later?)
|
|
// order
|
|
tag.order = opt.hierarchy.indexOf(name)
|
|
if(tag.order === -1) {
|
|
throw Error("Order of '"+name+"' not defined in hierarchy");
|
|
}
|
|
});
|
|
return opt;
|
|
}
|
|
|
|
// GENERAL UTILS
|
|
|
|
function get(o, args){ // path argments as separate string parameters
|
|
if(typeof args === 'string')
|
|
return o[args[0]];
|
|
var i = 0, l = args.length, u;
|
|
while((o = o[args[i++]]) != null && i < l){};
|
|
return i < l ? u : o;
|
|
}
|
|
|
|
function has(obj,prop){
|
|
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
}
|
|
|
|
// ELEMENT UTILS
|
|
|
|
function tag(e){
|
|
return (($(e)[0]||{}).nodeName||'').toLowerCase();
|
|
}
|
|
|
|
function joint(e, d){
|
|
d = (d? 'next' : 'previous') + 'Sibling';
|
|
return $(($(e)[0]||{})[d]);
|
|
}
|
|
|
|
var xssattr = /[^a-z:]/ig, xssjs = /javascript:/ig;
|
|
// url("javascript: // and all permutations
|
|
// stylesheets can apparently have XSS?
|
|
|
|
// create key val attributes object from elements attributes
|
|
function attrsAsObj(e, filterCb){
|
|
var attrObj = {};
|
|
(e = $(e)) && e.length && $(e[0].attributes||[]).each(function(value,name){
|
|
name = name.nodeName||name.name;
|
|
value = e.attr(name);
|
|
if(value.replace(xssattr,'').match(xssjs)){ e.removeAttr(name); return }
|
|
value = filterCb? filterCb(value,name,e) : value;
|
|
if(value !== undefined && value !== false)
|
|
attrObj[name] = value;
|
|
});
|
|
return attrObj;
|
|
}
|
|
|
|
// TODO: PERF testing - for loop to compare through?
|
|
function sameAttrs(a, b) {
|
|
return JSON.stringify(a.attr) === JSON.stringify(b.attr);
|
|
}
|
|
|
|
// INITIAL MUTATORS
|
|
|
|
function parseAndRemoveAttrs(a) {
|
|
a.attrs = [];
|
|
var tag = a.opt.convert[a.tag] || a.tag,
|
|
tOpt = a.opt.tags[tag];
|
|
a.attr = tOpt && attrsAsObj(a.$, function(value,name){
|
|
a.$.removeAttr(name);
|
|
if(tOpt.attrs[name.toLowerCase()]){
|
|
a.attrs.push(name)
|
|
return value;
|
|
}
|
|
});
|
|
}
|
|
|
|
function setAttrs(a){
|
|
var l = function(ind,name){
|
|
var t = name;
|
|
name = a.attrs? name : ind;
|
|
var value = a.attrs? a.attr[name.toLowerCase()] : t;
|
|
a.$.attr(name, value);
|
|
}
|
|
a.attrs? $(a.attrs.sort()).each(l) : $.each(a.attr,l);
|
|
}
|
|
|
|
function convert(a){
|
|
var t;
|
|
if(t = a.opt.convert[a.tag]){
|
|
a.$.replaceWith(a.$ = $('<'+ (a.tag = t.toLowerCase()) +'>').append(a.$.contents()));
|
|
}
|
|
}
|
|
|
|
// LOOPING (STATE MACHINE) MUTATORS
|
|
|
|
function exclude(a, addUnstable){
|
|
var t = get(a.opt, ['tags', a.tag]),
|
|
pt = get(a.opt, ['tags', tag(a.$.parent())]);
|
|
if(!t || (pt && get(pt, ['exclude', a.tag]))){
|
|
var c = a.$.contents();
|
|
a.$.replaceWith(c);
|
|
c.length===1 && c[0].a && addUnstable(c[0].a);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function moveSpaceUp(a, addUnstable){
|
|
var n = a.$[0];
|
|
if(moveSpace(n, true) + moveSpace(n, false)) {
|
|
// either front, back or both spaces moved
|
|
var c;
|
|
if(n.textContent==='') {
|
|
empty(a);
|
|
} else if((c = a.$.contents()[0]) && c.a) {
|
|
parentOrderWrap(c.a, addUnstable)
|
|
}
|
|
}
|
|
}
|
|
|
|
function moveSpace(n, bef) {
|
|
var childRe = bef? /^ / : / $/,
|
|
parentRe = bef? / $/ : /^ /,
|
|
c = bef? 'firstChild' : 'lastChild',
|
|
s = bef? 'previousSibling' : 'nextSibling';
|
|
sAdd = bef? 'after' : 'before';
|
|
pAdd = bef? 'prepend' : 'append';
|
|
if(!n || !n[c] || n[c].nodeType !== n.TEXT_NODE || !n[c].wholeText.match(childRe)) {
|
|
return 0;
|
|
}
|
|
if((n2 = n[s]) && !n.a.opt.blockTag(n.a)) {
|
|
if(n2.nodeType === 3 && !n2.textContent.match(parentRe)) {
|
|
n2.textContent = (bef?'':' ') + n2.textContent + (bef?' ':'');
|
|
} else if(n2.nodeType === 1) {
|
|
$(n2)[sAdd](' ');
|
|
}
|
|
} else if((n2 = n.parentNode) && !n.a.opt.blockTag(n.a)) {
|
|
$(n2)[pAdd](' ');
|
|
} else {
|
|
return 0;
|
|
}
|
|
n[c].textContent = n[c].wholeText.replace(childRe, '');
|
|
if(!n[c].wholeText.length)
|
|
$(n[c]).remove();
|
|
return 1;
|
|
}
|
|
|
|
function next(a, addUnstable, t){
|
|
var t = t || joint(a.$, true), sm;
|
|
if(!t.length || a.opt.blockTag(a))
|
|
return;
|
|
if(a.opt.spaceMerge && t.length===1 && t[0].nodeType === 3 && t[0].wholeText===' '){
|
|
if(!(t2 = joint(t, true)).length || a.opt.blockTag(t2[0].a))
|
|
return;
|
|
t.remove();
|
|
t2.prepend(' ');
|
|
return next(a, addUnstable, t2);
|
|
}
|
|
if(!t[0].a || a.tag !== t[0].a.tag || !sameAttrs(a, t[0].a))
|
|
return;
|
|
t.prepend(a.$.contents());
|
|
empty(a);
|
|
addUnstable(t[0].a);
|
|
(t = t.children(":first")).length && addUnstable(t[0].a);
|
|
}
|
|
|
|
function empty(a){
|
|
var t = a.opt.tags[a.tag];
|
|
if((!t || !t.empty) && !a.$.contents().length && !a.$[0].attributes.length){
|
|
a.$.remove();
|
|
return true; // NOTE true/false - different API than in exclude
|
|
}
|
|
}
|
|
|
|
function parentOrderWrap(a, addUnstable){
|
|
var parent = a.$.parent(), children = parent.contents(),
|
|
tags = a.opt.tags, ptag;
|
|
|
|
if(children.length===1 && children[0] === a.$[0]
|
|
&& (ptag=tags[tag(parent)]) && ptag.order > tags[a.tag].order){
|
|
parent.after(a.$);
|
|
parent.append(a.$.contents());
|
|
a.$.append(parent);
|
|
addUnstable(parent[0].a);
|
|
addUnstable(a);
|
|
}
|
|
}
|
|
})(); |