User:Quarl/util.js

// User:Quarl/util.js - miscellaneous utility functions for Wikipedia user scripts

// quarl 2006-01-09 initial version

// NON-NAMESPACED FUNCTION NAMES ARE DEPRECATED

//

/////////////////////////////////////////////////////////////

// STRING UTILITY FUNCTIONS

util = new Object();

util.trimSpaces = function(s) {

if (!s) return s;

s = s.replace(/^\s+/,'');

s = s.replace(/\s+$/,'');

return s;

}

trimspaces = util.trimSpaces;

util.trimLines = function(s) {

return s.replace(/^\n+/, ).replace(/\n+$/, );

}

trim_lines = util.trimLines;

util.stringQuoteEscape = function(str) {

if (!str) return str;

return "'" + str.replace(/\'/g, '\\\).replace(/\%27/g, '\\\) + "'";

}

string_quote_escape = util.stringQuoteEscape;

// wiki article name escaping

util.wpaEscape = function(s) {

// encodeURIComponent is better than 'escape' for unicode chars;

// it also escapes '+'.

// Don't escape ':'

return encodeURIComponent(s.replace(/ /g,'_')).replace(/%3A/g,':').replace(/%2F/g,'/');

}

wpaescape = wpaencode = util.wpaEscape;

util.wpaDecode = function(s) {

return decodeURIComponent(s).replace(/_/g,' ');

}

wpaunescape = wpadecode = util.wpaDecode;

util.urlGetPath = function(s) {

return s.replace(/^http:\/\/[^\/]+/, '');

}

url_getpath = util.urlGetPath;

// Return an authentication token useful to pass through URL for automatic

// edits, to prevent XSS attacks.

//

// requires md5.js

util.makeAuthToken = function() {

// I'd like to use something like readCookie('enwikiToken') + '%' +

// readCookie('enwiki_session')), but not sure how to get it cross-wiki

// compatible, so just use entire cookie for now.

return hex_md5('auth_token:'+Array.join(arguments, '%') +

'%%' + document.cookie);

}

makeAuthToken = util.makeAuthToken;

////////////////////////////////////////////////////////////

// DOM UTILITY FUNCTIONS

util.getElementsByClass = function(searchClass, node, tag) {

var classElements = [];

if (node == null)

node = document;

if (tag == null)

tag = '*';

var els = node.getElementsByTagName(tag);

var elsLen = els.length;

var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");

for (var i = 0; i < elsLen; i++) {

if (pattern.test(els[i].className) ) {

classElements.push(els[i]);

}

}

return classElements;

}

getElementsByClass = util.getElementsByClass;

util.addClass = function(node, cls) {

node.className += ' '+cls;

}

addClass = util.addClass;

util.removeClass = function(node, cls) {

node.className = node.className.replace(

new RegExp("(^|\\s)"+cls+"(\\s|$)",'g'), ' ');

}

removeClass = util.removeClass;

util.addNodeBefore = function(node, newnode) {

node.parentNode.insertBefore(newnode, node);

return newnode;

}

add_before = util.addNodeBefore;

util.addNodeBefore = function(node, newnode) {

if (node.nextSibling) {

node.parentNode.insertBefore(newnode, node.nextSibling);

} else {

node.parentNode.appendChild(newnode);

}

return newnode;

}

add_after = util.addNodeBefore;

// return nodes in [node_start, node_end)

util.getNodesInRange = function(node_start, node_end) {

var nodes = [];

while (node_start != node_end) {

nodes.push(node_start);

node_start = node_start.nextSibling;

if (!node_start) return null; // didn't reach node_end!

}

return nodes;

}

getNodesInRange = util.getNodesInRange;

util.removeNodesInRange = function(node_start, node_end) {

if (!node_end) {

alert("## removeNodesInRange: node_end==null");

return null;

}

if (!util.getNodesInRange(node_start, node_end)) {

alert("## removeNodesInRange: range does not terminate");

return null;

}

var parent = node_start.parentNode;

while (node_start != node_end) {

var n = node_start.nextSibling; // save before it gets clobbered

parent.removeChild(node_start);

node_start = n;

if (!node_start) return null;

}

}

removeNodesInRange = util.removeNodesInRange;

util.createHref = function(href, title, inner) {

var a = document.createElement('a');

a.href = href;

a.title = title;

a.innerHTML = inner;

return a;

}

createHref = util.createHref;

util.findHref = function(href) {

href = util.wpaEscape(util.wpaDecode(href));

var links=document.links;

for(i=0;i

// unescape and reescape to ensure canonical escaping

if (util.wpaEscape(util.wpaDecode(links[i].href)) == href) return links[i];

}

return null;

}

findHref = util.findHref;

// insert a new node as parent of node

util.insertNode = function(node, newNode) {

if (!node) return null;

node.parentNode.replaceChild(newNode, node);

newNode.appendChild(node);

return newNode;

}

insertNode = util.insertNode;

util.appendChildren = function(node, newNodes) {

for (var i in newNodes) {

node.appendChild(newNodes[i]);

}

}

appendChild = util.appendChild;

util.hookEventObj = function(obj, hookName, hookFunct) {

if (!obj) return;

if (obj.addEventListener)

obj.addEventListener(hookName, hookFunct, false);

else if (obj.attachEvent)

obj.attachEvent("on" + hookName, hookFunct);

}

hookEventObj = util.hookEventObj;

util.copyArray = function(a) {

var r = [];

for (var i=0; i < a.length; i++)

r[i] = a[i];

return r;

}

copyArray = util.copyArray;

// add a span around a node if there isn't one.

util.ensureSpan = function(node) {

if (node.parentNode.nodeName == 'SPAN') {

return node.parentNode;

}

return insertNode(node, document.createElement('span'));

}

ensureSpan = util.ensureSpan;

////////////////////////////////////////////////////////////

// STYLESHEET FUNCTIONS

util.addStylesheetRule = function(tag, style) {

var ss = document.styleSheets[0];

if (ss.insertRule) {

ss.insertRule(tag + '{' + style + '}', ss.cssRules.length);

} else if (ss.addRule) {

ss.addRule(tag, style);

}

}

addStylesheetRule = util.addStylesheetRule;

////////////////////////////////////////////////////////////

// AJAX FUNCTIONS

// cross-platform

util.HTTPClient = function() {

var http;

if(window.XMLHttpRequest) {

http = new XMLHttpRequest();

} else if (window.ActiveXObject) {

try {

http = new ActiveXObject("Msxml2.XMLHTTP");

} catch (e) {

try {

http = new ActiveXObject("Microsoft.XMLHTTP");

} catch (E) {

http = false;

}

}

}

return http;

}

HTTPClient = util.HTTPClient;

util.asyncDownloadXML = function(url, callback, props) {

var req = util.HTTPClient();

if (!req) return null;

// add optional arguments

if (props) {

for (var k in props) {

req[k] = props[k];

}

}

req.open("GET", url, true);

req.overrideMimeType('text/xml');

// using onload instead of onreadystatechange allows multiple asynchronous requests

// TODO: since we now have access to 'req' as a variable, we could change back.

// Is there any advantage to using onreadystatechange?

req.onload = function(event) {

var req = event.target;

if (req.readyState == 4) callback(req);

};

req.send(null);

return req;

}

asyncDownloadXML = util.asyncDownloadXML;

util.buildParams = function(paramArray) {

var params = '';

for (k in paramArray) {

v = paramArray[k];

// if v is a Boolean then the form was a checkbox.

// unchecked checkboxes should not add any input fields.

if (v == false) continue;

if (v == true) v = 'on';

params += '&' + k + '=' + encodeURIComponent(v);

}

params = params.replace(/^&/,'');

return params;

}

buildParams = util.buildParams;

util.addFormHiddenParams = function(newform, d) {

for (var k in d) {

v = d[k];

// if v is a Boolean then the form was a checkbox.

// unchecked checkboxes should not add any input fields.

if (v == false) continue;

if (v == true) v = 'on';

var t = document.createElement('input');

t.type = 'hidden';

t.name = k;

t.value = d[k];

newform.appendChild(t);

}

return newform;

}

addFormHiddenParams = util.addFormHiddenParams;

util.asyncPostXML = function(url, parameters, callback, props) {

var req = util.HTTPClient();

if (!req) return null;

if (typeof parameters != 'string') parameters = buildParams(parameters);

// add optional arguments

if (props) {

for (var k in props) {

req[k] = props[k];

}

}

req.open("POST", url, true);

req.overrideMimeType('text/xml');

req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

req.setRequestHeader("Content-length", parameters.length);

req.setRequestHeader("Connection", "close");

req.onload = function(event) {

var req = event.target;

if (req.readyState == 4) callback(req);

};

req.send(parameters);

return req;

}

asyncPostXML = util.asyncPostXML;

// Temporarily replace the content of statusNode with a non-clicakble, bolded string

// that shows we're doing something. statusNode should be the node that completely wraps

// an element that was just clicked.

util.buttonShowStatus = function(statusNode, statusText) {

if (!statusNode) return;

if (!statusText) {

// use tag to keep padding/margin/color/etc.

statusText = ''+statusNode.textContent+'...';

}

// Note: saving innerHTML doesn't work if we've messed with the document

// tree.

// statusNode.savedContent = statusNode.innerHTML;

// statusNode.innerHTML = statusText;

statusNode.savedContent = util.copyArray(statusNode.childNodes);

statusNode.innerHTML = statusText;

}

buttonShowStatus = util.buttonShowStatus;

util.buttonRestoreStatus = function(statusNode) {

if (statusNode && statusNode.savedContent) {

// statusNode.innerHTML = statusNode.savedContent;

statusNode.innerHTML = '';

util.appendChildren(statusNode, statusNode.savedContent);

}

}

buttonRestoreStatus = util.buttonRestoreStatus;

//

//