17223554 : xquared upgrade to 0.7

git-svn-id: http://xe-core.googlecode.com/svn/sandbox@4968 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
haneul 2008-11-24 08:52:19 +00:00
parent 5956e254e7
commit 7c3b336e41
59 changed files with 34562 additions and 8454 deletions

View file

@ -0,0 +1,391 @@
/**
* @namespace
*/
xq.validator = {}
/**
* @requires Xquared.js
* @requires Browser.js
* @requires rdom/Factory.js
*/
xq.validator.Base = xq.Class(/** @lends xq.validator.Base.prototype */{
/**
* @constructs
*/
initialize: function(curUrl, urlValidationMode, whitelist) {
xq.addToFinalizeQueue(this);
xq.asEventSource(this, "Validator", ["Preprocessing", "BeforeDomValidation", "AfterDomValidation", "BeforeStringValidation", "AfterStringValidation", "BeforeDomInvalidation", "AfterDomInvalidation", "BeforeStringInvalidation", "AfterStringInvalidation"]);
this.whitelist = whitelist || xq.predefinedWhitelist;
this.pRGB = xq.compilePattern("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
this.curUrl = curUrl;
this.curUrlParts = curUrl ? curUrl.parseURL() : null;
this.urlValidationMode = urlValidationMode;
},
/**
* Perform validation on given element
*
* @param {Element} element Target element. It is not affected by validation.
*
* @returns {String} Validated HTML string
*/
validate: function(element, dontClone) {
// DOM validation
element = dontClone ? element : element.cloneNode(true);
this._fireOnBeforeDomValidation(element);
this.validateDom(element);
this._fireOnAfterDomValidation(element);
// String validation
var html = {value: element.innerHTML};
this._fireOnBeforeStringValidation(html);
html.value = this.validateString(html.value);
this._fireOnAfterStringValidation(html);
return html.value;
},
validateDom: function(element) {throw "Not implemented";},
validateString: function(html) {throw "Not implemented";},
/**
* Perform invalidation on given element to make the designmode works well.
*
* @param {String} html HTML string.
* @returns {String} Invalidated HTML string
*/
invalidate: function(html) {
// Preprocessing
var html = {value: html};
this._fireOnPreprocessing(html);
// DOM invalidation
var element = document.createElement("DIV");
element.innerHTML = html.value;
this._fireOnBeforeDomInvalidation(element);
this.invalidateDom(element);
this._fireOnAfterDomInvalidation(element);
// String invalidation
html.value = element.innerHTML;
this._fireOnBeforeStringInvalidation(html);
html.value = this.invalidateString(html.value);
this._fireOnAfterStringInvalidation(html);
return html.value;
},
invalidateDom: function(element) {throw "Not implemented"},
invalidateString: function(html) {throw "Not implemented"},
/**
* em.class="underline" -> u
* span.class="strike" -> strike
*/
invalidateStrikesAndUnderlines: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
var nameOfClassName = xq.Browser.isTrident ? "className" : "class";
var underlines = xq.getElementsByClassName(rdom.getRoot(), "underline", "em");
var pUnderline = xq.compilePattern("(^|\\s)underline($|\\s)");
var lenOfUnderlines = underlines.length;
for(var i = 0; i < lenOfUnderlines; i++) {
rdom.replaceTag("u", underlines[i]).removeAttribute(nameOfClassName);
}
var strikes = xq.getElementsByClassName(rdom.getRoot(), "strike", "span")
var pStrike = xq.compilePattern("(^|\\s)strike($|\\s)");
var lenOfStrikes = strikes.length;
for(var i = 0; i < lenOfStrikes; i++) {
rdom.replaceTag("strike", strikes[i]).removeAttribute(nameOfClassName);
}
},
validateStrike: function(content) {
content = content.replace(/<strike(>|\s+[^>]*>)/ig, "<span class=\"strike\"$1");
content = content.replace(/<\/strike>/ig, "</span>");
return content;
},
validateUnderline: function(content) {
content = content.replace(/<u(>|\s+[^>]*>)/ig, "<em class=\"underline\"$1");
content = content.replace(/<\/u>/ig, "</em>");
return content;
},
replaceTag: function(content, from, to) {
return content.replace(new RegExp("(</?)" + from + "(>|\\s+[^>]*>)", "ig"), "$1" + to + "$2");
},
validateSelfClosingTags: function(content) {
return content.replace(/<(br|hr|img|value)([^>]*?)>/img, function(str, tag, attrs) {
return "<" + tag + attrs + " />"
});
},
validateFont: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
// It should be reversed to deal with nested elements
var fonts = element.getElementsByTagName('FONT');
var fontSizes = ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"];
var len = fonts.length - 1;
for(var i = len; i >= 0; i--) {
var font = fonts[i];
var color = font.getAttribute('color');
var backgroundColor = font.style.backgroundColor;
var face = font.getAttribute('face');
var size = fontSizes[parseInt(font.getAttribute('size')) % 8 - 1];
if(color || backgroundColor || face || size) {
var span = rdom.replaceTag("span", font);
span.removeAttribute('color');
span.removeAttribute('face');
span.removeAttribute('size');
if(color) span.style.color = color;
if(backgroundColor) span.style.backgroundColor = backgroundColor;
if(face) span.style.fontFamily = face;
if(size) span.style.fontSize = size;
}
}
},
invalidateFont: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
// It should be reversed to deal with nested elements
var spans = element.getElementsByTagName('SPAN');
var fontSizes = {"xx-small":1, "x-small":2, "small":3, "medium":4, "large":5, "x-large":6, "xx-large":7};
var len = spans.length - 1;
for(var i = len; i >= 0; i--) {
var span = spans[i];
if(span.className === "strike") continue;
var color = span.style.color;
var backgroundColor = span.style.backgroundColor;
var face = span.style.fontFamily;
var size = fontSizes[span.style.fontSize];
if(color || backgroundColor || face || size) {
var font = rdom.replaceTag("font", span);
font.style.cssText = "";
if(color) font.setAttribute('color', this.asRGB(color));
if(backgroundColor) font.style.backgroundColor = backgroundColor;
if(face) font.setAttribute('face', face);
if(size) font.setAttribute('size', size);
}
}
},
asRGB: function(color) {
if(color.indexOf("#") === 0) return color;
var m = this.pRGB.exec(color);
if(!m) return color;
var r = Number(m[1]).toString(16);
var g = Number(m[2]).toString(16);
var b = Number(m[3]).toString(16);
if(r.length === 1) r = "0" + r;
if(g.length === 1) g = "0" + g;
if(b.length === 1) b = "0" + b;
return "#" + r + g + b;
},
removeComments: function(content) {
return content.replace(/<!--.*?-->/img, '');
},
removeDangerousElements: function(element) {
var scripts = element.getElementsByTagName('SCRIPT');
for(var i = scripts.length - 1; i >= 0; i--) {
scripts[i].parentNode.removeChild(scripts[i]);
}
},
applyWhitelist: function(content) {
var whitelist = this.whitelist;
var allowedAttrs = null;
var p1 = xq.compilePattern("(^|\\s\")([^\"=]+)(\\s|$)", "g");
var p2 = xq.compilePattern("(\\S+?)=\"[^\"]*\"", "g");
return content.replace(new RegExp("(</?)([^>]+?)(>|\\s+([^>]*?)(\\s?/?)>)", "g"), function(str, head, tag, tail, attrs, selfClosing) {
if(!(allowedAttrs = whitelist[tag])) return '';
if(attrs) {
if(xq.Browser.isTrident) attrs = attrs.replace(p1, '$1$2="$2"$3');
var sb = [];
var m = attrs.match(p2);
for(var i = 0; i < m.length; i++) {
var name = m[i].split('=')[0];
if(allowedAttrs.indexOf(name) !== -1) sb.push(m[i]);
}
if(sb.length) {
attrs = sb.join(' ');
return head + tag + ' ' + attrs + selfClosing + '>';
} else {
return head + tag + selfClosing + '>';
}
} else {
return str;
}
});
},
// TODO: very expansive
makeUrlsRelative: function(content) {
var curUrl = this.curUrl;
var urlParts = this.curUrlParts;
var p1 = xq.compilePattern("(href|src)=\"([^\"]+)\"", "g");
var p2 = xq.compilePattern("^\\w+://");
// 1. find attributes and...
return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
if(attrs) {
// 2. validate URL part
attrs = attrs.replace(p1, function(str, name, url) {
// 3. first, make it absolute
var abs = null;
if(url.charAt(0) === '#') {
abs = urlParts.includeQuery + url;
} else if(url.charAt(0) === '?') {
abs = urlParts.includePath + url;
} else if(url.charAt(0) === '/') {
abs = urlParts.includeHost + url;
} else if(url.match(p2)) {
abs = url;
} else {
abs = urlParts.includeBase + url;
}
// 4. make it relative by removing same part
var rel = abs;
if(abs === urlParts.includeHost) {
rel = "/";
} else if(abs.indexOf(urlParts.includeQuery) === 0) {
rel = abs.substring(urlParts.includeQuery.length);
} else if(abs.indexOf(urlParts.includePath) === 0) {
rel = abs.substring(urlParts.includePath.length);
} else if(abs.indexOf(urlParts.includeBase) === 0) {
rel = abs.substring(urlParts.includeBase.length);
} else if(abs.indexOf(urlParts.includeHost) === 0) {
rel = abs.substring(urlParts.includeHost.length);
}
if(rel === '') rel = '#';
return name + '="' + rel + '"';
});
return head + attrs + tail + '>';
} else {
return str;
}
});
return content;
},
// TODO: very expansive
makeUrlsHostRelative: function(content) {
var curUrl = this.curUrl;
var urlParts = this.curUrlParts;
var p1 = xq.compilePattern("(href|src)=\"([^\"]+)\"", "g");
var p2 = xq.compilePattern("^\\w+://");
// 1. find attributes and...
return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
if(attrs) {
// 2. validate URL part
attrs = attrs.replace(p1, function(str, name, url) {
// 3. first, make it absolute
var abs = null;
if(url.charAt(0) === '#') {
abs = urlParts.includeQuery + url;
} else if(url.charAt(0) === '?') {
abs = urlParts.includePath + url;
} else if(url.charAt(0) === '/') {
abs = urlParts.includeHost + url;
} else if(url.match(p2)) {
abs = url;
} else {
abs = urlParts.includeBase + url;
}
// 4. make it relative by removing same part
var rel = abs;
if(abs === urlParts.includeHost) {
rel = "/";
} else if(abs.indexOf(urlParts.includeQuery) === 0 && abs.indexOf("#") !== -1) {
// same except for fragment-part?
rel = abs.substring(abs.indexOf("#"));
} else if(abs.indexOf(urlParts.includeHost) === 0) {
// same host?
rel = abs.substring(urlParts.includeHost.length);
}
if(rel === '') rel = '#';
return name + '="' + rel + '"';
});
return head + attrs + tail + '>';
} else {
return str;
}
});
return content;
},
// TODO: very expansive
makeUrlsAbsolute: function(content) {
var curUrl = this.curUrl;
var urlParts = this.curUrlParts;
var p1 = xq.compilePattern("(href|src)=\"([^\"]+)\"", "g");
var p2 = xq.compilePattern("^\\w+://");
// 1. find attributes and...
return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
if(attrs) {
// 2. validate URL part
attrs = attrs.replace(p1, function(str, name, url) {
var abs = null;
if(url.charAt(0) === '#') {
abs = urlParts.includeQuery + url;
} else if(url.charAt(0) === '?') {
abs = urlParts.includePath + url;
} else if(url.charAt(0) === '/') {
abs = urlParts.includeHost + url;
} else if(url.match(p2)) {
abs = url;
} else {
abs = urlParts.includeBase + url;
}
return name + '="' + abs + '"';
});
return head + attrs + tail + '>';
} else {
return str;
}
});
}
});

View file

@ -0,0 +1,18 @@
/**
* Creates and returns instance of browser specific implementation.
*
* @requires Xquared.js
* @requires validator/Base.js
* @requires validator/Trident.js
* @requires validator/Gecko.js
* @requires validator/Webkit.js
*/
xq.validator.Base.createInstance = function(curUrl, urlValidationMode, whitelist) {
if(xq.Browser.isTrident) {
return new xq.validator.Trident(curUrl, urlValidationMode, whitelist);
} else if(xq.Browser.isWebkit) {
return new xq.validator.Webkit(curUrl, urlValidationMode, whitelist);
} else {
return new xq.validator.Gecko(curUrl, urlValidationMode, whitelist);
}
}

View file

@ -0,0 +1,13 @@
/**
* @requires Xquared.js
* @requires validator/W3.js
*/
xq.validator.Gecko = xq.Class(xq.validator.W3,
/**
* @name xq.validator.Gecko
* @lends xq.validator.Gecko.prototype
* @extends xq.validator.W3
* @constructor
*/
{
});

View file

@ -0,0 +1,75 @@
/**
* @requires Xquared.js
* @requires validator/Base.js
*/
xq.validator.Trident = xq.Class(xq.validator.Base,
/**
* @name xq.validator.Trident
* @lends xq.validator.Trident.prototype
* @extends xq.validator.Base
* @constructor
*/
{
validateDom: function(element) {
this.removeDangerousElements(element);
this.validateFont(element);
},
validateString: function(html) {
try {
html = this.validateStrike(html);
html = this.validateUnderline(html);
html = this.performFullValidation(html);
} catch(ignored) {}
return html;
},
invalidateDom: function(element) {
this.invalidateFont(element);
this.invalidateStrikesAndUnderlines(element);
},
invalidateString: function(html) {
html = this.removeComments(html);
return html;
},
performFullValidation: function(html) {
html = this.lowerTagNamesAndUniformizeQuotation(html);
html = this.validateSelfClosingTags(html);
html = this.applyWhitelist(html);
if(this.urlValidationMode === 'relative') {
html = this.makeUrlsRelative(html);
} else if(this.urlValidationMode === 'host_relative') {
html = this.makeUrlsHostRelative(html);
} else if(this.urlValidationMode === 'absolute') {
// Trident always use absolute URL so we don't need to do anything.
//
// html = this.makeUrlsAbsolute(html);
}
return html;
},
lowerTagNamesAndUniformizeQuotation: function(html) {
this.pAttrQuotation1 = xq.compilePattern("\\s(\\w+?)=\\s+\"([^\"]+)\"", "mg");
this.pAttrQuotation2 = xq.compilePattern("\\s(\\w+?)=([^ \"]+)", "mg");
this.pAttrQuotation3 = xq.compilePattern("\\sNAME=\"(\\w+?)\" VALUE=\"(\\w+?)\"", "mg");
// Uniformize quotation, turn tag names and attribute names into lower case
html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) {
return "<" + closingMark + tagName.toLowerCase() + this.correctHtmlAttrQuotation(attrs) + ">";
}.bind(this));
return html;
},
correctHtmlAttrQuotation: function(html) {
html = html.replace(this.pAttrQuotation1, function (str, name, value) {return " " + name.toLowerCase() + '=' + '"' + value + '"'});
html = html.replace(this.pAttrQuotation2, function (str, name, value) {return " " + name.toLowerCase() + '=' + '"' + value + '"'});
html = html.replace(this.pAttrQuotation3, function (str, name, value) {return " name=\"" + name + "\" value=\"" + value + "\""});
return html;
}
});

View file

@ -0,0 +1,84 @@
/**
* @requires Xquared.js
* @requires validator/Base.js
*/
xq.validator.W3 = xq.Class(xq.validator.Base,
/**
* @name xq.validator.W3
* @lends xq.validator.W3.prototype
* @extends xq.validator.Base
* @constructor
*/
{
validateDom: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
this.removeDangerousElements(element);
rdom.removePlaceHoldersAndEmptyNodes(element);
this.validateFont(element);
},
validateString: function(html) {
try {
html = this.replaceTag(html, "b", "strong");
html = this.replaceTag(html, "i", "em");
html = this.validateStrike(html);
html = this.validateUnderline(html);
html = this.addNbspToEmptyBlocks(html);
html = this.performFullValidation(html);
html = this.insertNewlineBetweenBlockElements(html);
} catch(ignored) {}
return html;
},
invalidateDom: function(element) {
this.invalidateFont(element);
this.invalidateStrikesAndUnderlines(element);
},
invalidateString: function(html) {
html = this.replaceTag(html, "strong", "b");
html = this.replaceTag(html, "em", "i");
html = this.removeComments(html);
html = this.replaceNbspToBr(html);
return html;
},
performFullValidation: function(html) {
html = this.validateSelfClosingTags(html);
html = this.applyWhitelist(html);
if(this.urlValidationMode === 'relative') {
html = this.makeUrlsRelative(html);
} else if(this.urlValidationMode === 'host_relative') {
html = this.makeUrlsHostRelative(html);
} else if(this.urlValidationMode === 'absolute') {
html = this.makeUrlsAbsolute(html);
}
return html;
},
insertNewlineBetweenBlockElements: function(html) {
var blocks = new xq.DomTree().getBlockTags().join("|");
var regex = new RegExp("</(" + blocks + ")>([^\n])", "img");
return html.replace(regex, '</$1>\n$2');
},
addNbspToEmptyBlocks: function(content) {
var blocks = new xq.DomTree().getBlockTags().join("|");
var regex = new RegExp("<(" + blocks + ")>\\s*?</(" + blocks + ")>", "img");
return content.replace(regex, '<$1>&nbsp;</$2>');
},
replaceNbspToBr: function(content) {
var blocks = new xq.DomTree().getBlockTags().join("|");
// Safari replaces &nbsp; into \xA0
var regex = new RegExp("<(" + blocks + ")>(&nbsp;|\xA0)?</(" + blocks + ")>", "img");
var rdom = xq.rdom.Base.createInstance();
return content.replace(regex, '<$1>' + rdom.makePlaceHolderString() + '</$3>');
}
});

View file

@ -0,0 +1,145 @@
/**
* @requires Xquared.js
* @requires validator/W3.js
*/
xq.validator.Webkit = xq.Class(xq.validator.W3,
/**
* @name xq.validator.Webkit
* @lends xq.validator.Webkit.prototype
* @extends xq.validator.W3
* @constructor
*/
{
validateDom: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
this.removeDangerousElements(element);
rdom.removePlaceHoldersAndEmptyNodes(element);
this.validateAppleStyleTags(element);
},
validateString: function(html) {
try {
html = this.addNbspToEmptyBlocks(html);
html = this.performFullValidation(html);
html = this.insertNewlineBetweenBlockElements(html);
} catch(ignored) {}
return html;
},
invalidateDom: function(element) {
this.invalidateAppleStyleTags(element);
},
invalidateString: function(html) {
html = this.replaceTag(html, "strong", "b");
html = this.replaceTag(html, "em", "i");
html = this.removeComments(html);
html = this.replaceNbspToBr(html);
return html;
},
validateAppleStyleTags: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
var nodes = xq.getElementsByClassName(rdom.getRoot(), "apple-style-span");
for(var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if(node.style.fontStyle === "italic") {
// span -> em
node = rdom.replaceTag("em", node);
node.removeAttribute("class");
node.style.fontStyle = "";
} else if(node.style.fontWeight === "bold") {
// span -> strong
node = rdom.replaceTag("strong", node);
node.removeAttribute("class");
node.style.fontWeight = "";
} else if(node.style.textDecoration === "underline") {
// span -> em.underline
node = rdom.replaceTag("em", node);
node.className = "underline";
node.style.textDecoration = "";
} else if(node.style.textDecoration === "line-through") {
// span -> span.strike
node.className = "strike";
node.style.textDecoration = "";
} else if(node.style.verticalAlign === "super") {
// span -> sup
node = rdom.replaceTag("sup", node);
node.removeAttribute("class");
node.style.verticalAlign = "";
} else if(node.style.verticalAlign === "sub") {
// span -> sup
node = rdom.replaceTag("sub", node);
node.removeAttribute("class");
node.style.verticalAlign = "";
} else if(node.style.fontFamily) {
// span -> span font-family
node.removeAttribute("class");
}
}
},
invalidateAppleStyleTags: function(element) {
var rdom = xq.rdom.Base.createInstance();
rdom.setRoot(element);
// span.strike -> span, span... -> span
var spans = rdom.getRoot().getElementsByTagName("span");
for(var i = 0; i < spans.length; i++) {
var node = spans[i];
if(node.className == "strike") {
node.className = "Apple-style-span";
node.style.textDecoration = "line-through";
} else if(node.style.fontFamily) {
node.className = "Apple-style-span";
}
// TODO: bg/fg/font-size
}
// em -> span, em.underline -> span
var ems = rdom.getRoot().getElementsByTagName("em");
for(var i = 0; i < ems.length; i++) {
var node = ems[i];
node = rdom.replaceTag("span", node);
if(node.className === "underline") {
node.className = "apple-style-span";
node.style.textDecoration = "underline";
} else {
node.className = "apple-style-span";
node.style.fontStyle = "italic";
}
}
// strong -> span
var strongs = rdom.getRoot().getElementsByTagName("strong");
for(var i = 0; i < strongs.length; i++) {
var node = strongs[i];
node = rdom.replaceTag("span", node);
node.className = "Apple-style-span";
node.style.fontWeight = "bold";
}
// sup -> span
var sups = rdom.getRoot().getElementsByTagName("sup");
for(var i = 0; i < sups.length; i++) {
var node = sups[i];
node = rdom.replaceTag("span", node);
node.className = "Apple-style-span";
node.style.verticalAlign = "super";
}
// sub -> span
var subs = rdom.getRoot().getElementsByTagName("sub");
for(var i = 0; i < subs.length; i++) {
var node = subs[i];
node = rdom.replaceTag("span", node);
node.className = "Apple-style-span";
node.style.verticalAlign = "sub";
}
}
});