UnitFormat

By Ivan Rivera Last update Aug 2, 2008 — Installed 335 times. Daily Installs: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0

There are 1 previous version of this script.

// ==UserScript==
// @name           UnitFormat
// @namespace      http://brucknerite.net
// @description    Unit microformat processing and conversion.
// ==/UserScript==

// License:
//
// Copyright (c) 2007 Ivan Rivera
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

// Null unit name array
var nullUnits = [ 'null', 'roman', 'decimal', 'binary', 'octal', 'hex' ];

// Select all <abbr> elements with an "unit" class using XPath
var allUnits = document.evaluate(
    '//abbr[contains(@class,"unit")]',
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
// Send everyone of them off to Google for conversion
for (var i = 0; i < allUnits.snapshotLength; i++) {
    queryGoogle(allUnits.snapshotItem(i));
}

/**
 * Queries Google via xmlHttpRequest for unit conversions. Origin and 
 * destination unit are specified in the class attribute of <abbr> elements 
 * with the following syntax:
 *      class="unit [origin] [destination]"
 * where origin and destination are mutually interchangeable units in a format 
 * acceptable to Google Calc (whichever that means). The response is handled by 
 * processResponseData(unitElem, html, altUnit). Any kind of error (parsing,
 * connectivity or otherwise) should result in the function returning without
 * side effects.
 *
 * @param unitElem  <abbr> DOM element representing a unit microformat.
 */
function queryGoogle(unitElem) {
    var value = unitElem.getAttribute('title');
    var classes = unitElem.getAttribute('class').split(' ');
    if (classes.length < 3) {
        return;
    }
    var unit = classes[1];
    var alt = classes[2];
    var shouldParseNumber = true;
    if (nullUnits.indexOf(unit.toLowerCase()) > -1) {
        unit = '';
        shouldParseNumber = alt.indexOf('decimal') > -1;
    }
    GM_xmlhttpRequest({
        method: 'GET',
        url: 'http://www.google.com/search?q=' + value + '+' + escape(unit) + 
            '+in+' + escape(alt),
        headers: {
            'User-agent': 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071719 Firefox/3.0.1'
        },
        onload: function(response) {
            processResponseData(unitElem, response.responseText, alt, shouldParseNumber);
        }
    });
}

/**
 * Parses Google Calc's responses and creates an element containing the 
 * conversion in the form of an asterisk adjacent to the original unit
 * microformat. The generated HTML is unit-microformat compliant in itself,
 * but contains no target unit:
 *      <abbr title="[destination_unit_value]" class="unit [destination_unit]">
 *          <a title="[destination_unit_value] [destination_unit]">*</a>
 *      </abbr>
 *
 * @param unitElem          <abbr> DOM element representing a unit microformat.
 * @param html              HTML text with Google Calc's response to process.
 * @param altUnit           Destination unit name.
 * @param shouldParseNumber true if the result is expected to be decimal.
 */
function processResponseData(unitElem, html, altUnit, shouldParseNumber) {
    var re = new RegExp(' = .+</b></h2>', 'g');
    var matches = html.match(re);
    if (matches && matches.length > 0) {
        var value = shouldParseNumber ? 
            parseHtmlNumber(matches[0]) : 
            matches[0].split('</b>')[0].split('= ')[1];
        var badge = document.createElement('sup');
        badge.innerHTML = '<abbr title="' + value + '" class="unit ' + altUnit +
            '"><a title="' + value + ' ' + altUnit + '">*</a></abbr>';
        unitElem.parentNode.insertBefore(badge, unitElem.nextSibling);
    }
}

/**
 * Takes a number expressed as HTML text and returns the corresponding floating 
 * point number Parsing assumes a single number, maybe with presentational HTML 
 * interspersed, perhaps containing some power of ten. Not expecting more than 
 * one separator sign (decimal, maybe "," or ".").
 *
 * @param html        HTML text containing a number.
 * @paran precision   Decimal places for the result (default 2). 
 * @return            Parsed floating point number.
 */
function parseHtmlNumber(html, precision) {
    var mantissa = 0;
    var exponent = 0;
    var pexp = !precision ? 100 : Math.pow(10, precision);
    // Let's deal with the exponent first
    if (html.indexOf('<sup>') > -1) {
        supSplit = html.split('<sup>');
        exponent = parseInt(supSplit[1]);
        html = supSplit[0].split('�')[0];
        if (isNaN(exponent)) {
            exponent = 0;
        }
    }
    // Follow up with the mantissa
    html = html.replace(/<[^<>]+> ?/g, '')
        .replace(/[^0-9,.-]/g, '')
        .replace(/,/g, '.');
    mantissa = parseFloat(html);
    if (isNaN(mantissa)) {
        mantissa = 0;
    }
    // Adjust mantissa's fractional part to the desired precision
    var integer = mantissa > 0 ? Math.floor(mantissa) : Math.ceil(mantissa);
    var frac = mantissa - integer;
    var roundedfrac = Math.round(frac * pexp) / pexp;
    if (roundedfrac == 0 && integer == 0) {
        // We have rounded to zero! For now, let's revert the operation
        roundedfrac = frac;
    }
    mantissa = integer + roundedfrac;
    //return mantissa * Math.pow(10, exponent);
    var result = '' + mantissa;
    if (exponent != 0) {
        result += ' * 10^' + exponent;
    } 
    return result;
}