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<links.length;++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;

//

//