2011年11月7日 星期一

常用的 javascript function

在研究 htmlEditor 時, 先到 google blog, 看看 google 怎麼去處理他的 htmlEditor(Rich Editor), 在 trace javascript 時, 看到別人的常用function, 也許可以參考看看別人的寫法.

/**
* Author: Chris Wetherell
* BrowserDetector (object)
*
* A class for detecting version 5 browsers by the Javascript objects
* they support and not their user agent strings (which can be
* spoofed).
*
* Warning: Though slow to develop, browsers may begin to add
* DOM support in later versions which might require changes to this
* file.
*
* Warning: No one lives forever. Presumably.
*
* Typical usage:
* Detect = new BrowserDetector();
* if (Detect.IE()) //IE-only code...
*/
function BrowserDetector()
{

//IE 4+
this.IE = function()
{
try {
return this.Run(document.all && !document.contains) != false;
} catch (e) {
/* IE 5.01 doesn't support the 'contains' object and
fails the first test */
if (document.all) return true;
return false;
}
}

//IE 5.5+
this.IE_5_5_newer = function()
{
try {
return (this.Run(this.IE() && Array.prototype.pop && !this.OPERA()) !=
false);
} catch (e) {return false;}
}

//IE 5, Macintosh
this.IE_5_Mac = function()
{
try {
return (true == undefined);
} catch (e) {
return (document.all &&
document.getElementById &&
!document.mimeType &&
!this.OPERA()) != false;
}
}

//Opera 7+
this.OPERA = function()
{
try {
return this.Run(window.opera) != false;
} catch (e) {return false;}
}

//Gecko, actually Mozilla 1.2+
this.MOZILLA = function()
{
try {
return this.Run(
document.implementation
&& document.implementation.createDocument
&& !document.contains
&& !this.OPERA()
) != false;
} catch (e) {return false;}
}

//Safari
this.SAFARI = function()
{
try {
return this.Run(
document.implementation
&& document.implementation.createDocument
&& document.contains
) != false;
} catch (e) {return false;}
}

//Any browser which supports the W3C DOM
this.DOM = function()
{
return (document.getElementById);
}

this.Run = function(test)
{
if (test == undefined) {
return false;
} else {
return test;
}
}

// This uses useragent for finer detection. If people spoof it, it's their
// own fault when things break
this.geckoVersion = function() {
var matches = navigator.userAgent.match(/Gecko\/(\d*)/);
if (matches && matches.length > 1) {
return matches[1];
}

return null;
}
}


// Commonly-used functions, reduced.

function d(s) {return document.getElementById(s);}
function dE(o,s) {return o.getElementsByTagName(s);}

/**
* toggleDisplay()
*
* Will toggle the display property of the style object for any
* DOM element or object that supports style as a property.
*
* Warning: This'll wreak havoc if applied to <TR> elements. Those
* babies got different types "table-row" | "block" dependant on
* what browser's being used.
*
* Warning: Written in Texas. Yeehaw.
*
* Typical usage:
* toggleDisplay(document.getElementById("foo"));
*/
function toggleDisplay(o) {
var display = getStyle(o, 'display');

if (o.style) {
o.style.display =
(display != 'none') ? 'none' : getDisplayStyleByTagName(o);
}
}


function getDisplayStyleByTagName(o) {
var n = o.nodeName.toLowerCase();
return (n == 'span' ||
n == 'img' ||
n == 'a') ? 'inline' : 'block';
}


/**
* hideElement()
*
* Hides an element from view.
*
* Typical usage:
* hideElement(getElement("the-id-of-the-element"));
*/
function hideElement(o) {
if (o && o.style) o.style.display = 'none';
}


/**
* showElement()
*
* Shows an element that was hidden from view.
*
* Typical usage:
* showElement(getElement("the-id-of-the-element"));
*/
function showElement(o) {
if (o && o.style) o.style.display = getDisplayStyleByTagName(o);
}


/**
* getElement()
*
* Returns an element by its ID or shows an alert if it can't be found.
*
* Typical usage:
* getElement("the-id-of-the-element");
*/
function getElement(id) {
var e = d(id);
if (!e) {
alert('Cannot get element: ' + id);
}
return e;
}

/**
* setInnerHTML()
*
* Sets the innerHTML of an element or shows an alert if can't be set.
*
* Typical usage:
* setInnerHTML("the-id-of-the-element");
*/
function setInnerHTML(id, html) {
try {
getElement(id).innerHTML = html;
} catch (ex) {
alert('Cannot set inner HTML: ' + id);
}
}


/**
* setCssStyle()
*
* Sets the style of an element by its id or shows an alert if can't be set.
*
* Typical usage:
* setCssStyle("the-id-of-the-element", "display", "block");
*/
function setCssStyle(id, name, value) {
try {
getElement(id).style[name] = value;
} catch (ex) {
alert('Cannot set style: ' + id);
}
}


/**
* getStyle()
*
* Gets the computed style of any object.
*
* WARNING: Produces unexpected results in Safari. To achieve best
* results, explicitly set the style property for that browser when the
* element is rendered.
*
* Typical usage:
* getStyle(object, "display");
*/
function getStyle(el, style) {
if (!document.getElementById || !el) return;

if (document.defaultView
&& document.defaultView.getComputedStyle) {
return document.defaultView.
getComputedStyle(el, '').getPropertyValue(style);
} else if (el.currentStyle) {
return el.currentStyle[style];
} else {
return el.style.display;
}
}


/**
* getStyleAttribute()
*
* Returns the style attribute of the specified node.
*/
function getStyleAttribute(node) {
if (Detect.IE()) {
return node.getAttribute('style').value;
} else {
return node.getAttribute('style');
}
}


/*
* showProps()
*
* Displays all the properties for a given element
*/
function showProps(o) {
var s = '';
for (var p in o) {
s += p + ': '+ o[p] + '\n<br />';
}
document.write(s);
}


function setIFrameEvent(iframe, eventName, func) {
if (document.all) {
eval('getIFrameDocument(iframe).on' + eventName + ' = func;');
} else {
iframe.contentWindow.addEventListener(eventName, func, true);
}
}


function setIFrameBody(iframe, strStyle, innerHtml) {
if (!innerHtml) innerHtml = '';
if (innerHtml == '' && Detect.IE()) {
innerHtml = '<div></div>';
}
var doc = getIFrameDocument(iframe);
doc.open();
doc.write('<head></head><body style="' + strStyle + '">' +
innerHtml + '</body>');
doc.close();
}


function getIFrameDocument(iframe) {
if (Detect.IE()) {
return iframe.document;
} else {
return iframe.contentDocument;
}
}


function getIFrame(strId) {
if (Detect.IE()) {
return document.frames[strId];
} else {
return document.getElementById(strId);
}
}


function createElementandAppend(nodeName, strId, appendTo) {
var el = document.createElement(nodeName);
el.setAttribute('id', strId);
if (appendTo) {
appendTo.appendChild(el);
} else {
document.body.appendChild(el);
}
return el;
}


function createElementandInsertBefore(nodeName, strId, appendTo, sibling) {
var el = document.createElement(nodeName);
el.setAttribute('id', strId);
if (appendTo) {
appendTo.insertBefore(el, sibling);
} else {
document.body.insertBefore(el, sibling);
}
return el;
}


/**
* getXY()
*
* Returns the position of any element as an object.
*
* Typical usage:
* var pos = getXY(object);
* alert(pos.x + " " +pos.y);
*/
function getXY(el) {
var x = el.offsetLeft;
var y = el.offsetTop;
if (el.offsetParent != null) {
var pos = getXY(el.offsetParent);
x += pos.x;
y += pos.y;
}
return {x: x, y: y};
}


// The following 3 functions are taken from common.js
function hasClass(el, cl) {
if (el == null || el.className == null) return false;
var classes = el.className.split(' ');
for (var i = 0; i < classes.length; i++) {
if (classes[i] == cl) {
return true;
}
}
return false;
}


// Add a class to element
function addClass(el, cl) {
if (hasClass(el, cl)) return;
el.className += ' ' + cl;
}


// Remove a class from an element
function removeClass(el, cl) {
if (el.className == null) return;
var classes = el.className.split(' ');
var result = [];
var changed = false;
for (var i = 0; i < classes.length; i++) {
if (classes[i] != cl) {
if (classes[i]) { result.push(classes[i]); }
} else {
changed = true;
}
}
if (changed) { el.className = result.join(' '); }
}


function toggleClass(el, cl) {
if (hasClass(el, cl)) {
removeClass(el, cl);
} else {
addClass(el, cl);
}
}



function surroundFrameSelection(frame, tagName) {
var win = frame.contentWindow;
surroundSelection(win, tagName);
}


function surroundSelection(win, tagName) {
if (Detect.IE()) {
surroundSelection_IE(win.document, tagName);
} else {
var doc = (win.contentDocument) ? win.contentDocument : document;
var el = doc.createElement(tagName);
surroundSelection_DOM(win, el);
}
}


function insertNodeAtSelection(win, tag, fragment) {
if (Detect.IE()) {
var doc = win.document;
var range = doc.selection.createRange();
insertNodeAtSelection_IE(doc, tag, fragment.innerHTML);
} else {
var doc = (win.contentDocument) ? win.contentDocument : document;
var el = doc.createElement(tag);
insertNodeAtSelection_DOM(win, el, fragment);
}
}


function insertNodeAtSelection_IE(doc, tag, html) {
try {
var range = doc.selection.createRange();
var startTag = '<' + tag + '>';
var endTag = '</' + tag + '>';
var replaceString = startTag + html + endTag;

var isCollapsed = range.text == '';
range.pasteHTML(replaceString);

if (!isCollapsed) {
// move selection to html contained within the surrounding node
range.moveToElementText(range.parentElement().childNodes[0]);
range.select();
}
} catch (e) {
RichEdit.addDebugMsg('insertNodeAtSelection_IE() failed for "' + tag + '"');
}
}


function surroundSelection_IE(doc, tag) {
try {
var range = doc.selection.createRange();
var html = range.htmlText;

// get rid of beginning newline
if (html.substring(0, 2) == '\r\n') html = html.substring(2, html.length);

// resolve IE's special DIV cases
html = replaceEmptyDIVsWithBRs(html);

insertNodeAtSelection_IE(doc, tag, html);
} catch (e) {
RichEdit.addDebugMsg('surroundSelection_IE() failed for "' + tag + '"');
}
}


function surroundSelection_DOM(win, tag) {
try {
var sel = win.getSelection();
var range = sel.getRangeAt(0);
insertNodeAtSelection_DOM(win, tag, range.cloneContents());
} catch (e) {
RichEdit.addDebugMsg('surroundSelection_DOM() failed for "' + tag + '"');
}
}


/*
* This function was taken from The Mozilla Organization's Midas demo. It has
* been modified. In the future we may instead be able to use the
* surroundContents() method of the range object, but a bug exists as of
* 7/6/2004 that prohibits our use of it in Mozilla.
* (http://bugzilla.mozilla.org/show_bug.cgi?id=135928)
*/
function insertNodeAtSelection_DOM(win, insertNode, html)
{
// get current selection
var sel = win.getSelection();

// get the first range of the selection
// (there's almost always only one range)
var range = sel.getRangeAt(0);

// insert specified HTML into the node passed by argument
insertNode.appendChild(html);

// deselect everything
sel.removeAllRanges();

// remove content of current selection from document
range.deleteContents();

// get location of current selection
var container = range.startContainer;
var pos = range.startOffset;

// make a new range for the new selection
range = document.createRange();

var afterNode;

if (container.nodeType == 3 && insertNode.nodeType == 3) {
// if we insert text in a textnode, do optimized insertion
container.insertData(pos, insertNode.nodeValue);

} else {
if (container.nodeType == 3) {
// when inserting into a textnode
// we create 2 new textnodes
// and put the insertNode in between

var textNode = container;
container = textNode.parentNode;
var text = textNode.nodeValue;

// text before the split
var textBefore = text.substr(0, pos);
// text after the split
var textAfter = text.substr(pos);

var beforeNode = document.createTextNode(textBefore);
var afterNode = document.createTextNode(textAfter);

// insert the 3 new nodes before the old one
container.insertBefore(afterNode, textNode);
container.insertBefore(insertNode, afterNode);
container.insertBefore(beforeNode, insertNode);

// remove the old node
container.removeChild(textNode);
} else {
// else simply insert the node
afterNode = container.childNodes[pos];
container.insertBefore(insertNode, afterNode);
}
}

// select the modified html
range.setEnd(insertNode, insertNode.childNodes.length);
range.setStart(insertNode, insertNode);
sel.addRange(range);
}


/*
* getRangeAsDocumentFragment()
*
* Returns an HTML Document fragment representing the contents of the
* supplied selection range.
*/
function getRangeAsDocumentFragment(range) {
try {
if (Detect.IE()) {
var el = document.createElement('span');
el.innerHTML = range.htmlText;
return el;
} else {
return range.cloneContents();
}
} catch (e) {
RichEdit.addDebugMsg('--getRangeAsDocumentFragment() failed');
return null;
}
}

function setKeysetByEvent(e) {
// IE delivers a different keyset per key event type. Additionally,
// 'keydown' is different than 'keypress' but only in terms of the
// ctrl + shift combination. Ugh. Safari is more consistent. Gecko is
// right on.

// set up the globally shared variables
currentCtrlKeyPressedFromEvent = isCtrlKeyPressed(e);
currentShiftKeyPressedFromEvent = isShiftKeyPressed(e);
currentKeyFromEvent = getKey(e);

IE_CTRL_SHIFT_KEYSET = IE_KEYSET;
if (Detect.IE() && e && e.type == 'keydown') IE_CTRL_SHIFT_KEYSET = false;

BACKSPACE = (currentKeyFromEvent == BACKSPACE_KEYCODE);
DELETE = (Detect.IE()) ?
(currentKeyFromEvent == DELETE_KEYCODE)
: (getEvent(e).keyCode == 46 && getEvent(e).charCode == 0);
ESCAPE = (currentKeyFromEvent == ESC_KEYCODE);
RETURN = (currentKeyFromEvent == ENTER_KEYCODE);
SPACE = (currentKeyFromEvent == SPACE_KEYCODE);
TAB = (currentKeyFromEvent == TAB_KEYCODE);

LEFT_ARROW = (currentKeyFromEvent == LEFT_KEYCODE);
RIGHT_ARROW = (currentKeyFromEvent == RIGHT_KEYCODE);
UP_ARROW = (currentKeyFromEvent == UP_KEYCODE);
DOWN_ARROW = (currentKeyFromEvent == DOWN_KEYCODE);

DIGIT = (!currentShiftKeyPressedFromEvent &&
((currentKeyFromEvent >= 48 && currentKeyFromEvent <= 57) ||
(currentKeyFromEvent >= 96 && currentKeyFromEvent <= 105)));// keypad

// Alphabets
ALPHA = false;
if (e.type == 'keypress') {
ALPHA = ((currentKeyFromEvent >= 65 && currentKeyFromEvent <= 90) ||
(currentKeyFromEvent >= 97 && currentKeyFromEvent <= 122));
} else {
ALPHA = (currentKeyFromEvent >= 65 && currentKeyFromEvent <= 90);
}

CTRL_SHFT_A = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(1, e) :
isKeyPressedWithCtrlShift(65, e);

CTRL_SHFT_B = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(2, e) :
isKeyPressedWithCtrlShift(66, e);

CTRL_SHFT_D = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(4, e) :
isKeyPressedWithCtrlShift(68, e);

CTRL_SHFT_L = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(12, e) :
isKeyPressedWithCtrlShift(76, e);

CTRL_SHFT_P = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(16, e) :
isKeyPressedWithCtrlShift(80, e);

CTRL_SHFT_S = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(19, e) :
isKeyPressedWithCtrlShift(83, e);

CTRL_SHFT_T = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(20, e) :
isKeyPressedWithCtrlShift(84, e);

CTRL_SHFT_U = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(21, e) :
isKeyPressedWithCtrlShift(85, e);

CTRL_SHFT_Z = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(26, e) :
isKeyPressedWithCtrlShift(90, e);

CTRL_B = (IE_KEYSET) ?
isKeyPressedWithCtrl(66, e) : isKeyPressedWithCtrl(98, e);

CTRL_D = (IE_KEYSET) ?
isKeyPressedWithCtrl(68, e) : isKeyPressedWithCtrl(100, e);

CTRL_G = (IE_KEYSET) ?
isKeyPressedWithCtrl(71, e) : isKeyPressedWithCtrl(103, e);

CTRL_I = (IE_KEYSET) ?
isKeyPressedWithCtrl(73, e) : isKeyPressedWithCtrl(105, e);

CTRL_L = (IE_KEYSET) ?
isKeyPressedWithCtrl(76, e) : isKeyPressedWithCtrl(108, e);

CTRL_P = (IE_KEYSET) ?
isKeyPressedWithCtrl(80, e) && !isShiftKeyPressed(e) :
isKeyPressedWithCtrl(112, e) && !isShiftKeyPressed(e);

CTRL_S = (IE_KEYSET) ?
isKeyPressedWithCtrl(83, e) : isKeyPressedWithCtrl(115, e);

CTRL_Y = (IE_KEYSET) ?
isKeyPressedWithCtrl(89, e) : isKeyPressedWithCtrl(121, e);

CTRL_Z = (IE_KEYSET) ?
isKeyPressedWithCtrl(90, e) : isKeyPressedWithCtrl(122, e);

/*
We see for 'keycode' of the pressed key if the event-type is keydown or
keyup, and 'charcode' if the event-type is keypress.
*/
if (e.type == 'keydown' || e.type == 'keyup') {
PUNCTUATION =
// ! @ # $ % ^ & * ( )
(currentKeyFromEvent >= 48 && currentKeyFromEvent <= 57
&& currentShiftKeyPressedFromEvent) ||
// ; : + = , < _ - . > / ? ~ `
(currentKeyFromEvent >= 186 && currentKeyFromEvent <= 192) ||
// { [ } ] \ | ' "
(currentKeyFromEvent >= 219 && currentKeyFromEvent <= 222);
} else {
// Kepress event
var str = null;
if (Detect.IE()) {
str = String.fromCharCode(currentKeyFromEvent);
} else if (!(e.event_ && e.event_.isChar)) {
str = String.fromCharCode(e.charCode);
} else {
PUNCTUATION = false;
}

if (str) {
PUNCTUATION = PUNCTUATIONS_STR.indexOf(str) != -1;
}
}
}

/**
* isCtrlShiftKeyPressed()
*
* Determine by char index whether a certain key's been pressed in conjunction
* with the CTRL and SHIFT keys.
*/
function isKeyPressedWithCtrlShift(num, e) {
var key = getKeyAfterCtrlAndShift(e);
if (key) return (key == num);
return false;
}

function isKeyPressedWithCtrl(num, e) {
var key = getKeyAfterCtrl(e);
if (key) return (key == num);
return false;
}

function isKeyPressedWithShift(num, e) {
var key = getKeyAfterShift(e);
if (key) return (key == num);
return false;
}

// The following functions help manage some differing browser event models and
// key detection.
function getKeyAfterCtrl(e) {
if (currentCtrlKeyPressedFromEvent) { return currentKeyFromEvent; }
return false;
}

function getKeyAfterShift(e) {
if (currentShiftKeyPressedFromEvent) { return currentKeyFromEvent; }
return false;
}

function getKeyAfterCtrlAndShift(e) {
if (currentCtrlKeyPressedFromEvent && currentShiftKeyPressedFromEvent) {
return currentKeyFromEvent;
}
return false;
}

function isCtrlKeyPressed(e) {
return getEvent(e).ctrlKey;
}

function isShiftKeyPressed(e) {
return getEvent(e).shiftKey;
}

function isAltKeyPressed(e) {
return getEvent(e).altKey;
}

function getKey(e) {
var key = getEvent(e).keyCode;
if (!key) key = getEvent(e).charCode;
return key;
}

function getEventSource(evt) {
if (Detect.IE()) {
return evt.srcElement;
} else {
return evt.target;
}
}

function getEvent(e) {
return (!e) ? event : e;
}

//------------------------------------------------------------------------
// browser detection
var agent = navigator.userAgent.toLowerCase();
var is_ie = (agent.indexOf('msie') != -1);
var is_konqueror = (agent.indexOf('konqueror') != -1);
var is_safari = (agent.indexOf('safari') != -1) || is_konqueror;
var is_nav = !is_ie && !is_safari && (agent.indexOf('mozilla') != -1);
var is_win = (agent.indexOf('win') != -1);
delete agent;


var BACKSPACE_KEYCODE = 8;
var COMMA_KEYCODE = 188; // ',' key
var DEBUG_KEYCODE = 68; // 'D' key
var DELETE_KEYCODE = 46;
var DOWN_KEYCODE = 40; // DOWN arrow key
var ENTER_KEYCODE = 13; // ENTER key
var ESC_KEYCODE = 27; // ESC key
var LEFT_KEYCODE = 37; // LEFT arrow key
var RIGHT_KEYCODE = 39; // RIGHT arrow key
var SPACE_KEYCODE = 32; // space bar
var TAB_KEYCODE = 9; // TAB key
var UP_KEYCODE = 38; // UP arrow key
var SHIFT_KEYCODE = 16;

//------------------------------------------------------------------------
// Assertions
// DEPRECATED: Use assert.js
//------------------------------------------------------------------------
/**
* DEPRECATED: Use assert.js
*/
function raise(msg) {
if (typeof Error != 'undefined') {
throw new Error(msg || 'Assertion Failed');
} else {
throw (msg);
}
}

/**
* DEPRECATED: Use assert.js
*
* Fail() is useful for marking logic paths that should
* not be reached. For example, if you have a class that uses
* ints for enums:
*
* MyClass.ENUM_FOO = 1;
* MyClass.ENUM_BAR = 2;
* MyClass.ENUM_BAZ = 3;
*
* And a switch statement elsewhere in your code that
* has cases for each of these enums, then you can
* "protect" your code as follows:
*
* switch(type) {
* case MyClass.ENUM_FOO: doFooThing(); break;
* case MyClass.ENUM_BAR: doBarThing(); break;
* case MyClass.ENUM_BAZ: doBazThing(); break;
* default:
* Fail("No enum in MyClass with value: " + type);
* }
*
* This way, if someone introduces a new value for this enum
* without noticing this switch statement, then the code will
* fail if the logic allows it to reach the switch with the
* new value, alerting the developer that he should add a
* case to the switch to handle the new value he has introduced.
*
* @param {string} opt_msg to display for failure
* DEFAULT: "Assertion failed".
*/
function Fail(opt_msg) {
if (opt_msg === undefined) opt_msg = 'Assertion failed';
if (IsDefined(DumpError)) DumpError(opt_msg + '\n');
raise(opt_msg);
}

/**
* DEPRECATED: Use assert.js
*
* Asserts that an expression is true (non-zero and non-null).
*
* Note that it is critical not to pass logic
* with side-effects as the expression for AssertTrue
* because if the assertions are removed by the
* JSCompiler, then the expression will be removed
* as well, in which case the side-effects will
* be lost. So instead of this:
*
* AssertTrue( criticalComputation() );
*
* Do this:
*
* var result = criticalComputation();
* AssertTrue(result);
*
* @param {anything} expression to evaluate.
* @param {string} opt_msg to display if the assertion fails.
*
*/
function AssertTrue(expression, opt_msg) {
if (!expression) {
if (opt_msg === undefined) opt_msg = 'Assertion failed';
Fail(opt_msg);
}
}

/**
* DEPRECATED: Use assert.js
*
* Asserts that two values are the same.
*
* @param {anything} val1
* @param {anything} val2
* @param {string} opt_msg to display if the assertion fails.
*/
function AssertEquals(val1, val2, opt_msg) {
if (val1 != val2) {
if (opt_msg === undefined) {
opt_msg = 'AssertEquals failed: <' + val1 + '> != <' + val2 + '>';
}
Fail(opt_msg);
}
}

/**
* DEPRECATED: Use assert.js
*
* Asserts that a value is of the provided type.
*
* AssertType(6, Number);
* AssertType("ijk", String);
* AssertType([], Array);
* AssertType({}, Object);
* AssertType(ICAL_Date.now(), ICAL_Date);
*
* @param {anything} value
* @param {constructor function} type
* @param {string} opt_msg to display if the assertion fails.
*/
function AssertType(value, type, opt_msg) {
// for backwards compatability only
if (typeof value == type) return;

if (value || value == '') {
try {
if (type == AssertTypeMap[typeof value] || value instanceof type) return;
} catch (e) { /* failure, type was an illegal argument to instanceof */ }
}
if (opt_msg === undefined) {
if (typeof type == 'function') {
var match = type.toString().match(/^\s*function\s+([^\s\{]+)/);
if (match) type = match[1];
}
opt_msg = 'AssertType failed: <' + value + '> not typeof '+ type;
}
Fail(opt_msg);
}

var AssertTypeMap = {
'string' : String,
'number' : Number,
'boolean' : Boolean
};

/**
* DEPRECATED: Use assert.js
*
* Asserts that the number of arguments to a
* function is num. For example:
*
* function myFunc(one, two, three) [
* AssertNumArgs(3);
* ...
* }
*
* myFunc(1, 2); // assertion fails!
*
* Note that AssertNumArgs does not take the function
* as an argument; it is simply used in the context
* of the function.
*
* @param {int} number of arguments expected.
* @param {string} opt_msg to display if the assertion fails.
*/
function AssertNumArgs(num, opt_msg) {
var caller = AssertNumArgs.caller; // This is not supported in safari 1.0
if (caller && caller.arguments.length != num) {
if (opt_msg === undefined) {
opt_msg = caller.name + ' expected ' + num + ' arguments '
+ ' but received ' + caller.arguments.length;
}
Fail(opt_msg);
}
}

//------------------------------------------------------------------------
// Cookies
//------------------------------------------------------------------------
var ILLEGAL_COOKIE_CHARS_RE = /[\s;]/;
/**
* Sets a cookie.
* The max_age can be -1 to set a session cookie. To expire cookies, use
* ExpireCookie() instead.
*
* @param name The cookie name.
* @param value The cookie value.
* @param opt_max_age The max age in seconds (from now). Use -1 to set a
* session cookie. If not provided, the default is -1 (i.e. set a session
* cookie).
* @param opt_path The path of the cookie, or null to not specify a path
* attribute (browser will use the full request path). If not provided, the
* default is '/' (i.e. path=/).
* @param opt_domain The domain of the cookie, or null to not specify a domain
* attribute (brower will use the full request host name). If not provided,
* the default is null (i.e. let browser use full request host name).
* @return Void.
*/
function SetCookie(name, value, opt_max_age, opt_path, opt_domain) {
value = '' + value;
AssertTrue((typeof name == 'string' &&
typeof value == 'string' &&
!name.match(ILLEGAL_COOKIE_CHARS_RE) &&
!value.match(ILLEGAL_COOKIE_CHARS_RE)),
'trying to set an invalid cookie');

if (!IsDefined(opt_max_age)) opt_max_age = -1;
if (!IsDefined(opt_path)) opt_path = '/';
if (!IsDefined(opt_domain)) opt_domain = null;

var domain_str = (opt_domain == null) ? '' : ';domain=' + opt_domain;
var path_str = (opt_path == null) ? '' : ';path=' + opt_path;

var expires_str;

// Case 1: Set a session cookie.
if (opt_max_age < 0) {
expires_str = '';

// Case 2: Expire the cookie.
// Note: We don't tell people about this option in the function doc because
// we prefer people to use ExpireCookie() to expire cookies.
} else if (opt_max_age == 0) {
// Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert
// it to local time, and if the local time is before Jan 1, 1970, then the
// browser will ignore the Expires attribute altogether.
var pastDate = new Date(1970, 1 /*Feb*/, 1); // Feb 1, 1970
expires_str = ';expires=' + pastDate.toUTCString();

// Case 3: Set a persistent cookie.
} else {
var futureDate = new Date(Now() + opt_max_age * 1000);
expires_str = ';expires=' + futureDate.toUTCString();
}

document.cookie = name + '=' + value + domain_str + path_str + expires_str;
}

/** Returns the value for the first cookie with the given name
* @param name : string.
* @return a string or the empty string if no cookie found.
*/
function GetCookie(name) {
var nameeq = name + '=';
var cookie = String(document.cookie);
for (var pos = -1; (pos = cookie.indexOf(nameeq, pos + 1)) >= 0;) {
var i = pos;
// walk back along string skipping whitespace and looking for a ; before
// the name to make sure that we don't match cookies whose name contains
// the given name as a suffix.
while (--i >= 0) {
var ch = cookie.charAt(i);
if (ch == ';') {
i = -1; // indicate success
break;
} else if (' \t'.indexOf(ch) < 0) {
break;
}
}
if (-1 === i) { // first cookie in the string or we found a ;
var end = cookie.indexOf(';', pos);
if (end < 0) { end = cookie.length; }
return cookie.substring(pos + nameeq.length, end);
}
}
return '';
}


//------------------------------------------------------------------------
// Time
//------------------------------------------------------------------------
function Now() {
return (new Date()).getTime();
}

//------------------------------------------------------------------------
// Dynamic HTML/DOM utilities
//------------------------------------------------------------------------
// Gets a element by its id, may return null
function MaybeGetElement(win, id) {
return win.document.getElementById(id);
}

// Same as MaybeGetElement except that it throws an exception if it's null
function GetElement(win, id) {
var el = win.document.getElementById(id);
if (!el) {
DumpError('Element ' + id + ' not found.');
}
return el;
}

// Gets elements by its id/name
// IE treats getElementsByName as searching over ids, while Moz use names.
// so tags must have both id and name as the same string
function GetElements(win, id) {
return win.document.getElementsByName(id);
}

// Show/hide an element.
function ShowElement(el, show) {
el.style.display = show ? '' : 'none';
}

// Show/hide a block element.
// ShowElement() doesn't work if object has an initial class with display:none
function ShowBlockElement(el, show) {
el.style.display = show ? 'block' : 'none';
}

// Show/hide an inline element.
// ShowElement() doesn't work when an element starts off display:none.
function ShowInlineElement(el, show) {
el.style.display = show ? 'inline' : 'none';
}

// Append a new HTML element to a HTML node.
function AppendNewElement(win, parent, tag) {
var e = win.document.createElement(tag);
parent.appendChild(e);
return e;
}

// Create a new TR containing the given td's
function Tr(win, tds) {
var tr = win.document.createElement('TR');
for (var i = 0; i < tds.length; i++) {
tr.appendChild(tds[i]);
}
return tr;
}

// Create a new TD, with an optional colspan
function Td(win, opt_colspan) {
var td = win.document.createElement('TD');
if (opt_colspan) {
td.colSpan = opt_colspan;
}
return td;
}


// Check if an element has a given class
function HasClass(el, cl) {
if (el == null || el.className == null) return false;
var classes = el.className.split(' ');
for (var i = 0; i < classes.length; i++) {
if (classes[i] == cl) {
return true;
}
}
return false;
}

// Add a class to element
function AddClass(el, cl) {
if (HasClass(el, cl)) return;
el.className += ' ' + cl;
}

// Remove a class from an element
function RemoveClass(el, cl) {
if (el.className == null) return;
var classes = el.className.split(' ');
var result = [];
var changed = false;
for (var i = 0; i < classes.length; i++) {
if (classes[i] != cl) {
if (classes[i]) { result.push(classes[i]); }
} else {
changed = true;
}
}
if (changed) { el.className = result.join(' '); }
}

// Performs an in-order traversal of the tree rooted at the given node
// (excluding the root node) and returns an array of nodes that match the
// given selector. The selector must implement the method:
//
// boolean select(node);
//
// This method is a generalization of the DOM method "getElementsByTagName"
//
function GetElementsBySelector(root, selector) {
var nodes = [];
for (var child = root.firstChild; child; child = child.nextSibling) {
AddElementBySelector_(child, selector, nodes);
}
return nodes;
}

// Recursive helper for GetElemnetsBySelector()
function AddElementBySelector_(root, selector, nodes) {
// First test the parent
if (selector.select(root)) {
nodes.push(root);
}

// Then recurse through the children
for (var child = root.firstChild; child; child = child.nextSibling) {
AddElementBySelector_(child, selector, nodes);
}
}

//------------------------------------------------------------------------
// Window/screen utilities
// TODO: these should be renamed (e.g. GetWindowWidth to GetWindowInnerWidth
// and moved to geom.js)
//------------------------------------------------------------------------
// Get page offset of an element
function GetPageOffsetLeft(el) {
var x = el.offsetLeft;
if (el.offsetParent != null)
x += GetPageOffsetLeft(el.offsetParent);
return x;
}

// Get page offset of an element
function GetPageOffsetTop(el) {
var y = el.offsetTop;
if (el.offsetParent != null)
y += GetPageOffsetTop(el.offsetParent);
return y;
}

// Get page offset of an element
function GetPageOffset(el) {
var x = el.offsetLeft;
var y = el.offsetTop;
if (el.offsetParent != null) {
var pos = GetPageOffset(el.offsetParent);
x += pos.x;
y += pos.y;
}
return {x: x, y: y};
}

function GetPageOffsetRight(el) {
return GetPageOffsetLeft(el) + el.offsetWidth;
}

function GetPageOffsetBottom(el) {
return GetPageOffsetTop(el) + el.offsetHeight;
}

// Get the y position scroll offset.
function GetScrollTop(win) {
// all except Explorer
if ('pageYOffset' in win) {
return win.pageYOffset;
}
// Explorer 6 Strict Mode
else if ('documentElement' in win.document &&
'scrollTop' in win.document.documentElement) {
return win.document.documentElement.scrollTop;
}
// other Explorers
else if ('scrollTop' in win.document.body) {
return win.document.body.scrollTop;
}

return 0;
}

// Get the x position scroll offset.
function GetScrollLeft(win) {
// all except Explorer
if ('pageXOffset' in win) {
return win.pageXOffset;
}
// Explorer 6 Strict Mode
else if ('documentElement' in win.document &&
'scrollLeft' in win.document.documentElement) {
return win.document.documentElement.scrollLeft;
}
// other Explorers
else if ('scrollLeft' in win.document.body) {
return win.document.body.scrollLeft;
}

return 0;
}

//------------------------------------------------------------------------
// String utilities
//------------------------------------------------------------------------
// Do html escaping
var amp_re_ = /&/g;
var lt_re_ = /</g;
var gt_re_ = />/g;

// Convert text to HTML format. For efficiency, we just convert '&', '<', '>'
// characters.
// Note: Javascript >= 1.3 supports lambda expression in the replacement
// argument. But it's slower on IE.
// Note: we can also implement HtmlEscape by setting the value
// of a textnode and then reading the 'innerHTML' value, but that
// that turns out to be slower.
// Params: str: String to be escaped.
// Returns: The escaped string.
function HtmlEscape(str) {
if (!str) return '';
return str.replace(amp_re_, '&amp;').replace(lt_re_, '&lt;').
replace(gt_re_, '&gt;').replace(quote_re_, '&quot;');
}

/** converts html entities to plain text. It covers the most common named
* entities and numeric entities.
* It does not cover all named entities -- it covers &{lt,gt,amp,quot,nbsp}; but
* does not handle some of the more obscure ones like &{ndash,eacute};.
*/
function HtmlUnescape(str) {
if (!str) return '';
return str.
replace(/&#(\d+);/g,
function(_, n) { return String.fromCharCode(parseInt(n, 10)); }).
replace(/&#x([a-f0-9]+);/gi,
function(_, n) { return String.fromCharCode(parseInt(n, 16)); }).
replace(/&(\w+);/g, function(_, entity) {
entity = entity.toLowerCase();
return entity in HtmlUnescape.unesc ? HtmlUnescape.unesc[entity] : '?';
});
}
HtmlUnescape.unesc = { lt: '<', gt: '>', quot: '"', nbsp: ' ', amp: '&' };

// Escape double quote '"' characters in addition to '&', '<', '>' so that a
// string can be included in an HTML tag attribute value within double quotes.
// Params: str: String to be escaped.
// Returns: The escaped string.
var quote_re_ = /\"/g;

var JS_SPECIAL_RE_ = /[\'\\\r\n\b\"<>&]/g;

function JSEscOne_(s) {
if (!JSEscOne_.js_escs_) {
var escapes = {};
escapes['\\'] = '\\\\';
escapes['\''] = '\\047';
escapes['\n'] = '\\n';
escapes['\r'] = '\\r';
escapes['\b'] = '\\b';
escapes['\"'] = '\\042';
escapes['<'] = '\\074';
escapes['>'] = '\\076';
escapes['&'] = '\\046';

JSEscOne_.js_escs_ = escapes;
}

return JSEscOne_.js_escs_[s];
}

// converts multiple ws chars to a single space, and strips
// leading and trailing ws
var spc_re_ = /\s+/g;
var beg_spc_re_ = /^ /;
var end_spc_re_ = / $/;
function CollapseWhitespace(str) {
if (!str) return '';
return str.replace(spc_re_, ' ').replace(beg_spc_re_, '').
replace(end_spc_re_, '');
}

var newline_re_ = /\r?\n/g;
var spctab_re_ = /[ \t]+/g;
var nbsp_re_ = /\xa0/g;

function HtmlifyNewlines(str) {
if (!str) return '';
return str.replace(newline_re_, '<br>');
}

// URL encodes the string.
function UrlEncode(str) {
return encodeURIComponent(str);
}

function Trim(str) {
if (!str) return '';
return str.replace(/^\s+/, '').replace(/\s+$/, '');
}

function EndsWith(str, suffix) {
if (!str) return !suffix;
return (str.lastIndexOf(suffix) == (str.length - suffix.length));
}

// Check if a string is empty
function IsEmpty(str) {
return CollapseWhitespace(str) == '';
}

// Check if a character is a letter
function IsLetterOrDigit(ch) {
return ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9'));
}

// Check if a character is a space character
function IsSpace(ch) {
return (' \t\r\n'.indexOf(ch) >= 0);
}

//------------------------------------------------------------------------
// TextArea utilities
//------------------------------------------------------------------------

function SetCursorPos(win, textfield, pos) {
if (IsDefined(textfield.selectionEnd) &&
IsDefined(textfield.selectionStart)) {
// Mozilla directly supports this
textfield.selectionStart = pos;
textfield.selectionEnd = pos;

} else if (win.document.selection && textfield.createTextRange) {
// IE has textranges. A textfield's textrange encompasses the
// entire textfield's text by default
var sel = textfield.createTextRange();

sel.collapse(true);
sel.move('character', pos);
sel.select();
}
}

//------------------------------------------------------------------------
// Array utilities
//------------------------------------------------------------------------
// Find an item in an array, returns the key, or -1 if not found
function FindInArray(array, x) {
for (var i = 0; i < array.length; i++) {
if (array[i] == x) {
return i;
}
}
return -1;
}

// Inserts an item into an array, if it's not already in the array
function InsertArray(array, x) {
if (FindInArray(array, x) == -1) {
array[array.length] = x;
}
}

// Delete an element from an array
function DeleteArrayElement(array, x) {
var i = 0;
while (i < array.length && array[i] != x)
i++;
array.splice(i, 1);
}

function GetEventTarget(/*Event*/ ev) {
// Event is not a type in IE; IE uses Object for events
// AssertType(ev, Event, 'arg passed to GetEventTarget not an Event');
return ev.srcElement || ev.target;
}

//------------------------------------------------------------------------
// Misc
//------------------------------------------------------------------------
// Check if a value is defined
function IsDefined(value) {
return (typeof value) != 'undefined';
}

function GetKeyCode(event) {
var code;
if (event.keyCode) {
code = event.keyCode;
} else if (event.which) {
code = event.which;
}
return code;
}

// define a forid function to fetch a DOM node by id.
function forid_1(id) {
return document.getElementById(id);
}
function forid_2(id) {
return document.all[id];
}

/**
* Fetch an HtmlElement by id.
* DEPRECATED: use $ in dom.js
*/
var forid = document.getElementById ? forid_1 : forid_2;



function log(msg) {
/* a top level window is its own parent. Use != or else fails on IE with
* infinite loop.
*/
try {
if (window.parent != window && window.parent.log) {
window.parent.log(window.name + '::' + msg);
return;
}
} catch (e) {
// Error: uncaught exception: Permission denied to get property Window.log
}
var logPane = forid('log');
if (logPane) {
var logText = '<p class=logentry><span class=logdate>' + new Date() +
'</span><span class=logmsg>' + msg + '</span></p>';
logPane.innerHTML = logText + logPane.innerHTML;
} else {
window.status = msg;
}
}


// ===================================================================
// HTML utilities
// ===================================================================


function removeNewlinesFromTags(s) {
s = s.replace(/<[^>]+>/g, function(ref) {
ref = ref.replace(/\r/g, '');
ref = ref.replace(/\n/g, ' ');
return ref;
});
return s;
}

function replaceEmptyDIVsWithBRs(s) {
// Remove line feeds to avoid weird spacing in Edit HTML mode
s = s.replace(/[\r]/g, '');

/*
* sometimes IE's pasteHTML method of the range object will insert
* troublesome newlines that aren't useful
*/
if (Detect.IE()) {
var blockElementsPattern = new RegExp(
'\n<(' +
'BLOCKQUOTE' + '|' +
'LI' + '|' +
'OBJECT' + '|' +
'H[\d]' + '|' +
'FORM' + '|' +
'TR' + '|' +
'TD' + '|' +
'TH' + '|' +
'TBODY' + '|' +
'THEAD' + '|' +
'TFOOT' + '|' +
'TABLE' + '|' +
'DL' + '|' +
'DD' + '|' +
'DT' + '|' +
'DIV' + '|' +
')([^>]*)>', 'gi'
);
s = s.replace(blockElementsPattern, '<$1$2>');
}

// remove newlines from inside of tags
s = removeNewlinesFromTags(s);

// convert newlines to HTML line breaks
s = s.replace(/\n/g, '<BR>');

// Attempt to persist relative paths
if (Detect.IE()) {
s = addRelativePathPlaceholders(s);
}

/*
* Append html string to element in order to remove DIVs as nodes
* rather than using string manipulation
*/
var tmp = document.createElement('div');
tmp.style.display = 'none';

// we have to wrap the string inside of a div b/c of this weird IE bug
// that causes the entire innerHTML to become empty if the first element
// is an object tag.
tmp.innerHTML = '<div>' + s + '</div>';
tmp = tmp.childNodes[0];

// determine which DIVs have NO attributes
var plainDIVs = [];
var divs = tmp.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
var hasAttributes = false;
var attrs = divs[i].attributes;
for (var x = 0; x < attrs.length; x++) {
var val = attrs[x].specified;
if (val) {
hasAttributes = true;
break;
}
}
if (!hasAttributes) plainDIVs.push(divs[i]);
}

// strip DIVs with no attributes, but keep their content!
for (var i = 0; i < plainDIVs.length; i++) {
var grandparent = plainDIVs[i].parentNode;
if (grandparent) {
plainDIVs[i].innerHTML += '<BR>';
adoptGrandchildrenFromParent(document, plainDIVs[i]);
}
}

// remove newlines
var html = tmp.innerHTML;
html = html.replace(/[\r|\n]/g, '');

// Remove dummyURL in order to make links and images with relative links
// appear as requested and defeat IE's auto-complete
if (Detect.IE()) {
var NON_EXISTENT_URLPattern = new RegExp(NON_EXISTENT_URL, 'gi');
html = html.replace(NON_EXISTENT_URLPattern, '');
}

return html;
}

function cleanMSHTML(s) {
// Remove IE's inserted comments when text is pasted into the IFRAME
s = s.replace(/<!--StartFragment -->&nbsp;/g, '');

// Try to transform into well-formed markup
s = cleanHTML(s);

s = s.replace(/<br \/>/gi, '\n');

// Remove IE's blockquote styling
s = s.replace(/<blockquote dir=\"ltr" style=\"MARGIN-RIGHT: 0px\">/g,
'<blockquote style="margin-top:0;margin-bottom:0;">');

return s;
}

/*
* cleanHTML()
*
* Attempts to transform ill-formed HTML into well-formed markup.
*/
function cleanHTML(s) {
// make node names lower-case and add quotes to attributes
s = cleanNodesAndAttributes(s);
// Midas adds colors to <br> tags! TODO: take newline conversion elsewhere
if (Detect.MOZILLA()) s = s.replace(/<br [^>]+>/gi, '\n');
// make single nodes XHTML compatible
s = s.replace(/<(hr|br)>/gi, '<$1 \/>');
// make img nodes XHTML compatible
s = s.replace(/<(img [^>]+)>/gi, '<$1 \/>');
s = addClosingEmbedTags(s);
return s;
}

/*
* addClosingEmbedTags()
*
* Adds a closing embed tag if there isn't already one.
*/

function addClosingEmbedTags(s) {
// first strip away any closing embed tags if they exist. This should only
// happen if the HTML code was pasted into the compose mode.
s = s.replace(/<\/embed>/gi, '');
// Then add the closing tag.
s = s.replace(/<(embed [^>]+)>/gi, '<$1></embed>');
return s;
}

/*
* cleanNodesAndAttributes()
*
* Attempts to transform node names to lower-case and add double-quotes to
* HTML element node attributes.
*/
function cleanNodesAndAttributes(s) {
// Get all of the start tags
var htmlPattern = new RegExp('<[ ]*([\\w]+).*?>', 'gi');
s = s.replace(htmlPattern, function(ref) {
var cleanStartTag = ''; // for storing the result

// Separate the tag name from its attributes
var ref = ref.replace('^<[ ]*', '<'); // remove beginning whitespace
var ndx = ref.search(/\s/); // returns index of first match of whitespace
var tagname = ref.substring(0 , ndx);
var attributes = ref.substring(ndx, ref.length);

// Make tag name lower case
if (ndx == -1) return ref.toLowerCase(); // no attr/value pairs (i.e. <p>)
cleanStartTag += tagname.toLowerCase();

// Clean up attribute/value pairs
var pairs = attributes.match(/[\w]+\s*=\s*("[^"]*"|[^">\s]*)/gi);
if (pairs) {
for (var t = 0; t < pairs.length; t++) {
var pair = pairs[t];
var ndx = pair.search(/=/); // index of first match of equals (=)

// Make attribute names lower case
var attrname = pair.substring(0, ndx).toLowerCase();

// Put double-quotes around values that don't have them
var attrval = pair.substring(ndx, pair.length);
var wellFormed = new RegExp('=[ ]*"[^"]*"', 'g');
if (!wellFormed.test(attrval)) {
var attrvalPattern = new RegExp('=(.*?)', 'g');
attrval = attrval.replace(attrvalPattern, '=\"$1');
// there's an IE bug that prevent this endquote from being appended
// after the backreference. no, seriously.
attrval += '"';
}
// join the attribute parts
var attr = attrname + attrval;
cleanStartTag += ' ' + attr;
}
}
cleanStartTag += '>';

return cleanStartTag;
});

// Makes all of the end tags lower case
s = s.replace(/<\/\s*[\w]*\s*>/g, function(ref) {return ref.toLowerCase();});

return s;
}


/*
* convertAllFontsToSpans()
*
* Attempts to transform deprecated FONT nodes into well-formed XHTML-compliant
* markup.
*/
function convertAllFontsToSpans(s) {
startTagPattern = RegExp('<[^/]*font [^<>]*>', 'gi');
var StartTags = s.match(startTagPattern);
if (StartTags) {
for (var i = 0; i < StartTags.length; i++) {
// adjacent tags get lost in some regexp searches in some browsers, so
// we'll catch 'em here
if (StartTags[i].indexOf('>') > 1) innerStartTags =
StartTags[i].split('>');
for (var x = 0; x < innerStartTags.length; x++) {
if (innerStartTags[x] == '') continue;
var thisTag = innerStartTags[x] + '>';
modifiedStartTag = convertTagAttributeToStyleValue(thisTag,
'face',
'font-family');
modifiedStartTag = convertTagAttributeToStyleValue(modifiedStartTag,
'size',
'font-size');
modifiedStartTag = convertTagAttributeToStyleValue(modifiedStartTag,
'color',
'color');
s = s.replace(thisTag, modifiedStartTag);

}
}
}
s = s.replace(/<font>/gi, '<span>');
s = s.replace(/<font ([^>]*)>/gi, '<span $1>');
s = s.replace(/<\/font>/gi, '</span>');

// clean up extra spaces
s = s.replace(/<span[ ]+style/gi, '<span style');
return s;
}


/*
* convertTagAttributeToStyleValue()
*
* Attempts to transfer specified HTML attributes into the 'style' attribute for
* the supplied start tag.
*/
function convertTagAttributeToStyleValue(s, attrName, styleAttrName) {
// Get the style attribute value to convert
attributePattern = new RegExp(attrName + '="([^"]*)"', 'gi');
var matched = s.match(attributePattern);
if (!matched) return s;
var attrValue = RegExp.$1;

// remove the old attribute
s = s.replace(attributePattern, '');

// add value as new style attribute value
if (attrValue) {
if (attrName == 'size') attrValue = convertFontSizeToSpan(attrValue);
stylePattern = new RegExp('(<[^>]*style="[^"]*)("[^>]*>)', 'gi');
if (stylePattern.test(s)) {
var style = RegExp.$1;
if (style.indexOf(';') == -1) style += ';';
s = s.replace(stylePattern, style + styleAttrName + ':' + attrValue +
';$2');
} else {
tagPattern = new RegExp('(<[^\/][^>]*)(>)', 'gi');
s = s.replace(tagPattern, '$1 style="' + styleAttrName + ':' + attrValue +
';"$2');
}

//prevent colors with RGB values from aggregating with keyword / hex colors
var colorPattern = new RegExp('(color\\:[\\s]*rgb[^;]*;)color\\:[^;]*;',
'gi');
if (colorPattern.test(s)) {
s = s.replace(colorPattern, '$1');
}
}

return s;
}


/*
* convertAllSpansToFonts()
*
* Attempts to transform well-formed SPAN nodes into WYSIWYG-acceptable formats.
*/
function convertAllSpansToFonts(s) {
startTagPattern = RegExp('<[^\/]*span [^>]*>', 'gi');
var StartTags = s.match(startTagPattern);
if (StartTags) {
for (var i = 0; i < StartTags.length; i++) {
// adjacent tags get lost in some regexp searches in some browsers, so
// we'll catch 'em here
if (StartTags[i].indexOf('>') > 1) {
innerStartTags = StartTags[i].split('>');
}
for (x = 0; x < innerStartTags.length; x++) {
if (innerStartTags[x] == '') continue;
var thisTag = innerStartTags[x] + '>';
modifiedStartTag = convertTagStyleValueToAttribute(thisTag,
'font-family',
'face');
modifiedStartTag = convertTagStyleValueToAttribute(modifiedStartTag,
'font-size',
'size');
modifiedStartTag = convertTagStyleValueToAttribute(modifiedStartTag,
'color',
'color');

var lastTwoCharsPattern = new RegExp(' >$', 'gim');
modifiedStartTag = modifiedStartTag.replace(lastTwoCharsPattern, '>');
modifiedStartTag = modifiedStartTag.replace(/<span /gi, '<span ');

s = s.replace(thisTag, modifiedStartTag);
}
}
}
s = s.replace(/<span ([^>]*)>/gi, '<font $1>');
s = s.replace(/<\/span>/gi, '</font>');
s = s.replace(/<span>/gi, '<font>');

return s;
}


/*
* convertTagStyleValueToAttribute()
*
* Attempts to transfer specified values within the 'style' attribute to single
* HTML attributes for the supplied start tag.
*/
function convertTagStyleValueToAttribute(s, styleVal, attrName) {
// Get the style attribute value to convert
stylePattern = new RegExp('style="[^"]*' + styleVal + ':([^;]*)[^"]*"', 'gi');
var matched = s.match(stylePattern);
if (!matched) return s;
attrValue = RegExp.$1;

if (attrValue) {

attrValue = Trim(attrValue); // extra spaces will cause problems in IE

if (styleVal == 'color') {
var rgbPattern = new RegExp(
'rgb\\([ ]*[\\d]*[ ]*,[ ]*[\\d]*[ ]*,[ ]*[\\d]*[ ]*\\)', 'gi');
if (rgbPattern.test(attrValue)) {
return s; //TODO: add RGB to Hex conversion later
}
}
if (styleVal == 'font-size') {
attrValue = convertSpanSizeToFont(attrValue);
}
// remove the old style attribute
valuePattern = new RegExp(
'(style="[^"]*)(' + styleVal + ':[^;]*)[;]*([^"]*")', 'gi');
s = s.replace(valuePattern, '$1$3');

// add value as new attribute
stylePattern = new RegExp('(<[^>]*)(style="[^>]*>)', 'gi');
s = s.replace(stylePattern, '$1' + attrName + '="' + attrValue + '" $2');
}

//remove empty style pairs
s = s.replace(/style=""/gi, '');

return s;
}

var FONT_SIZE_CONVERSIONS = [
['5', '180%'],
['4', '130%'],
['3', '100%'],
['2', '85%'],
['1', '78%']
];

function convertFontSizeToSpan(size) {
return convertFontandSpanSizes(0, 1, size);
}

function convertSpanSizeToFont(size) {
return convertFontandSpanSizes(1, 0, size);
}

function convertFontandSpanSizes(beforeIndex, afterIndex, size) {
var conv = FONT_SIZE_CONVERSIONS;
size = Trim(size);
for (z = 0; z < conv.length; z++) {
if (size == conv[z][beforeIndex]) {
size = conv[z][afterIndex];
break;
}
}
return size;
}

function Trim(s) {
return s.replace(/^\s+/, '').replace(/\s+$/, '');
}


/**
* Escape any attributes found in the given html source that could be an XSS
* attack vector.
* @param {string} s HTML source.
* @return {string} Escaped HTML source.
*/
function escapeXssVectors(s) {
var htmlDiv = document.createElement('DIV');
htmlDiv.innerHTML = s;
escapeXssVectorsFromChildren_(htmlDiv);
return htmlDiv.innerHTML;
}

/**
* Recursive helper for escapeXssVectors. Escapes attributes of any children of
* the given element that are also elements, then recurses on that child.
* @param {Element} elem Element whose children should be escaped.
*/
function escapeXssVectorsFromChildren_(elem) {
for (var node = elem.firstChild; node != null; node = node.nextSibling) {
if (node.nodeType == 1) { // 1 == ELEMENT

if (node.nodeName.toLowerCase() == 'iframe') {
// If src attribute contains scripting, it could be an XSS attack
// (see {@bug 3256288}), so rename it to blogger_src so it won't get
// executed when the html is rendered inside the editor. To be extra
// safe, we just whitelist values starting with http(s):, so that it
// catches javascript:, vbscript:, data:, and any other tricks that
// may come up in the future.
var xssSafeRegExp = /^\s*https?:/i;
var srcValue = node.getAttribute('src');
if (srcValue && !xssSafeRegExp.test(srcValue)) {
node.removeAttribute('src');
node.setAttribute('blogger_src', srcValue);
}
}

// Rename any attributes starting with "on" to "blogger_on"+whatever to
// prevent the event handler code from ever executing, as that could be an
// XSS attack.
var eventAttrs = [];
for (var i = 0; i < node.attributes.length; i++) {
var attrNode = node.attributes[i];
if (!attrNode.specified) {
continue;
}
var attrName = attrNode.name.toLowerCase();
if (/^on/.test(attrName)) {
eventAttrs.push(attrName);
}
}
for (var j = 0; j < eventAttrs.length; j++) {
var attrValue = node.getAttribute(eventAttrs[j]);
node.removeAttribute(eventAttrs[j]);
node.setAttribute('blogger_' + eventAttrs[j], attrValue);
}

escapeXssVectorsFromChildren_(node);

}
}
}

/**
* Restore any attributes found in the given html source that were escaped via
* escapeXssVectors.
* @param {string} s Escaped HTML source.
* @return {string} Restored HTML source.
*/
function restoreXssVectors(s) {
if (Detect.IE()) {
// @bug 4227170 : IE collapses line breaks when you insert source into
// innerHTML, so replace them temporarily with <br> while we restore the
// placeholders. (Not a problem when escaping xss because we convert \n to
// <br> before escaping.)
s = s.replace(/\n/g, '<br>');
}

var htmlDiv = document.createElement('DIV');
htmlDiv.innerHTML = s;
restoreXssVectorsFromChildren_(htmlDiv);
s = htmlDiv.innerHTML;

if (Detect.IE()) {
// Convert temporary <br> back to line breaks.
s = s.replace(/<br>/gi, '\n');
}

return s;
}

/**
* Recursive helper for restoreXssVectors. Restores attributes of any children
* of the given element that are also elements, then recurses on that child.
* @param {Element} elem Element whose children should be restored.
*/
function restoreXssVectorsFromChildren_(elem) {
for (var node = elem.firstChild; node != null; node = node.nextSibling) {
if (node.nodeType == 1) { // 1 == ELEMENT

if (node.nodeName.toLowerCase() == 'iframe') {
var srcValue = node.getAttribute('blogger_src');
if (srcValue) {
node.removeAttribute('blogger_src');
node.setAttribute('src', srcValue);
}
}

var eventAttrs = [];
for (var i = 0; i < node.attributes.length; i++) {
var attrNode = node.attributes[i];
if (!attrNode.specified) {
continue;
}
var attrName = attrNode.name.toLowerCase();
if (/^blogger_on/.test(attrName)) {
eventAttrs.push(attrName);
}
}
for (var j = 0; j < eventAttrs.length; j++) {
var attrValue = node.getAttribute(eventAttrs[j]);
node.removeAttribute(eventAttrs[j]);
node.setAttribute(eventAttrs[j].substr(8), attrValue);
}

restoreXssVectorsFromChildren_(node);

}
}
}

沒有留言:

張貼留言

Facebook 留言板