Advertisement
QonQon

Universal metric translator FTFY

Jul 4th, 2017
221
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        Universal metric translator
  3. // @namespace   https://bennyjacobs.nl/userscripts/Universal-metric-translator
  4. // @description Automatically converts imperial units to metric units
  5. // @include     about:addons
  6. // @include     http*
  7. // @include     https*
  8. // @version     2.1.0
  9. // @grant       none
  10. // ==/UserScript==
  11.  
  12. // Flags: global, insensitive
  13. var createTransformationRegEx = function(unit) {
  14.     return new RegExp(
  15.         '\\s?'
  16.  
  17.       + '((?:\\d+(?:,\\d+)*)(?:\\.\\d+)?' // eg 9.23 or 23 or 0.34 or 2,204.6
  18.       + '|\\d*(?:\\.\\d+)'  // eg .34 but not 0.34
  19.       + '|(?:\\d+(?:,\\d+)*\\s)?\\d+(?:,\\d+)*/\\d+(?:,\\d+)*)' // 1 1/2 or 1/4, common with imperial
  20.  
  21.       + '(?:\\s*' + unit
  22.       + '\\b(?!(\\s\\[|\\]))' // prevent infinite replacement
  23.       // + '|\(?=\\s*(?:to|and|-)[\\d\\./\\s]+' + unit + '\\b)'
  24.       + ')'
  25.      ,"gi");
  26. };
  27.  
  28. // Sources:
  29. // https://en.wikipedia.org/wiki/Imperial_units
  30. // https://en.wikipedia.org/wiki/Metre
  31. // https://en.wikipedia.org/wiki/Square_metre
  32. // https://en.wikipedia.org/wiki/Litre
  33. var tranformationTable = [
  34.  
  35.     // Temperature
  36.     {
  37.         from: '(?:F|fahrenheit|fahrenheits|degrees F|degrees fahrenheit)',
  38.         to: 'ā„ƒ',
  39.         convert: function(fahrenheits){
  40.             return ((fahrenheits - 32) / 1.8).toFixed(2);
  41.         }
  42.     },
  43.  
  44.     // Distance
  45.     {
  46.         from: 'thou',
  47.         to: 'm',
  48.         convert: 25.4 * 1e-6,
  49.     }, {
  50.         from: '(?:inch(?:es|e)?)',
  51.         to: 'm',
  52.         convert: 25.4 * 1e-3,
  53.     }, {
  54.         from: '(?:(?:feets?|foot))',
  55.         to: 'm',
  56.         convert: 0.3048,
  57.     }, {
  58.         from: '(?:yards?|yd)',
  59.         to: 'm',
  60.         convert: 0.9144,
  61.     }, {
  62.         from: 'chains?',
  63.         to: 'm',
  64.         convert: 20.1168,
  65.     }, {
  66.         from: '(?:furlongs?|fur)',
  67.         to: 'm',
  68.         convert: 201.168,
  69.     }, {
  70.         from: 'miles?',
  71.         to: 'm',
  72.         convert: 1.609344 * 1e3,
  73.     }, {
  74.         from: 'leagues?',
  75.         to: 'm',
  76.         convert: 4.828032 * 1e3,
  77.     },
  78.  
  79.     // Maritime distances
  80.     {
  81.         from: '(?:fathoms?|ftm)',
  82.         to: 'm',
  83.         convert: 1.853184,
  84.     }, {
  85.         from: 'cables?',
  86.         to: 'm',
  87.         convert: 185.3184,
  88.     }, {
  89.         from: 'nautical\\smiles?', // Note: two backslashes as we are escaping a javascript string
  90.         to: 'm',
  91.         convert: 1.853184 * 1e3,
  92.     },
  93.  
  94.     // Gunter's survey units (17th century onwards)
  95.     {
  96.         from: 'link',
  97.         to: 'm',
  98.         convert: 0.201168,
  99.     }, {
  100.         from: 'rod',
  101.         to: 'm',
  102.         convert: 5.0292,
  103.     }, {
  104.         from: 'chain',
  105.         to: 'm',
  106.         convert: 20.1168,
  107.     },
  108.  
  109.     // Area
  110.     {
  111.         from: 'acres?',
  112.         to: 'kmĀ²',
  113.         convert: 4.0468564224,
  114.     },
  115.  
  116.     // Volume
  117.     {
  118.         from: '(?:fluid ounces?|fl oz)',
  119.         to: 'L',
  120.         convert: 28.4130625 * 1e-3,
  121.     }, {
  122.         from: 'gill?',
  123.         to: 'L',
  124.         convert: 142.0653125 * 1e-3,
  125.     }, {
  126.         from: '(?:pints?|pt)',
  127.         to: 'L',
  128.         convert: 0.56826125,
  129.     }, {
  130.         from: 'quarts?',
  131.         to: 'L',
  132.         convert: 1.1365225,
  133.     }, {
  134.         from: 'gal(?:lons?)?',
  135.         to: 'L',
  136.         convert: 4.54609,
  137.     },
  138.  
  139.     //Weight
  140.     {
  141.         from: 'grains?',
  142.         to: 'g',
  143.         convert: 64.79891 * 1e-3,
  144.     }, {
  145.         from: 'drachm',
  146.         to: 'g',
  147.         convert: 1.7718451953125,
  148.     }, {
  149.         from: '(?:ounces?|oz)',
  150.         to: 'g',
  151.         convert: 28.349523125,
  152.    }, {
  153.        // from: 'lbs?|pounds?', // Pound is ambiguous. It can be a currency. Therefore we don't touch it.
  154.        // Actually, since it would be displayed as
  155.        //   "It costs 1 pound [453.59 g]."
  156.        //   the metric translation can just be ignored by the reader.
  157.        //   I'm leaving it out anyways. lbs is usually used in written text anyways so it covers most cases.
  158.        from: 'lbs?',
  159.        to: 'g',
  160.        convert: 453.59,
  161.    }, {
  162.         from: 'stones?',
  163.         to: 'g',
  164.         convert: 6.35029318  * 1e3,
  165.     }, {
  166.         from: 'quarters?',
  167.         to: 'g',
  168.         convert: 12.70058636 * 1e3,
  169.     }, {
  170.         from: 'hundredweights?',
  171.         to: 'g',
  172.         convert: 50.80234544 * 1e3,
  173.     },
  174.     //  A 'ton' might belong here, but there exist a metric ton and a imperial ton.
  175.    
  176.     // Qon commment: A metric ton is sometimes spelled metric tonne or just tonne though.
  177.     // https://en.wikipedia.org/wiki/Ton
  178. ];
  179.  
  180. tranformationTable.forEach(function (transformationRule) {
  181.     transformationRule.regex = createTransformationRegEx(transformationRule.from);
  182. });
  183.  
  184. var replaceSubstring = function(originalText, index, length, replacement) {
  185.     var before_substring = originalText.substring(0, index);
  186.     var after_substring = originalText.substring(index+length);
  187.     return before_substring + replacement + after_substring;
  188. };
  189.  
  190. function round_number(num, dec) {
  191.     return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
  192. }
  193.  
  194. // The transformText function is idempotent.
  195. // Repeated calls on the output will do nothing. Only the first invocation has any effect.
  196. // The input will be returned on repeated calls.
  197. var transformText = function(text) {
  198.     tranformationTable.forEach(function (transformationRule) {
  199.        transformationRule.regex.lastIndex = 0;
  200.         for(var match; match = transformationRule.regex.exec(text);) {
  201.  
  202.             // console.log(match, parseFloat(match[1], 10))
  203.             var old_value, new_value;
  204.  
  205.             // if the number is written like 1 1/4 instead of 1.25 then:
  206.             if(/\//.test(match[1])) {
  207.                 old_value = match[1].split(' ')
  208.                 if(old_value.length == 2) {
  209.                     var a = old_value[1].split('/')
  210.                     old_value[1] = parseFloat(a[0].replace(/,/g, ''), 10) / parseFloat(a[1].replace(/,/g, ''), 10)
  211.                     old_value = parseFloat(old_value[0].replace(/,/g, ''), 10) + old_value[1]
  212.                 } else
  213.                     old_value = parseFloat(old_value[0].replace(/,/g, ''), 10)
  214.             } else {
  215.                 old_value = parseFloat(match[1].replace(/,/g, ''), 10)
  216.             }
  217.  
  218.             if(typeof transformationRule.convert == 'function') {
  219.                 new_value = transformationRule.convert(old_value);
  220.             } else {
  221.                 new_value = old_value * transformationRule.convert;
  222.             }
  223.  
  224.             var new_unit = transformationRule.to;
  225.             if(new_unit === 'g' || new_unit === 'L' || new_unit === 'm')
  226.             {
  227.                 if(new_value > 1e12) {
  228.                     new_unit = 'T' + new_unit
  229.                     new_value /= 1e12
  230.                 } else if (new_value > 1e9) {
  231.                     new_unit = 'G' + new_unit
  232.                     new_value /= 1e9
  233.                 } else if (new_value > 1e6) {
  234.                     // if(new_unit === 'g') new_unit = 'tonne' else
  235.                     new_unit = 'M' + new_unit
  236.                     new_value /= 1e6
  237.                 } else if (new_value > 1e3) {
  238.                     new_unit = 'k' + new_unit
  239.                     new_value /= 1e3
  240.                 } else if (new_value < 1e-9) {
  241.                     new_unit = 'p' + new_unit
  242.                     new_value /= 1e-12
  243.                 } else if (new_value < 1e-6) {
  244.                     new_unit = 'n' + new_unit
  245.                     new_value /= 1e-9
  246.                 } else if (new_value < 1e-3) {
  247.                     new_unit = 'Āµ' + new_unit
  248.                     new_value /= 1e-6
  249.                 } else if (new_value < 1e-2) {
  250.                     new_unit = 'm' + new_unit
  251.                     new_value /= 1e-3
  252.                 } else if (new_value < 1 && (new_unit !== 'g')) {
  253.                     new_unit = 'c' + new_unit
  254.                     new_value /= 1e-2
  255.                 }
  256.             }
  257.             // function significantDigits(old, new) {
  258.             //     old.replace(/^[^1-9]*/, '').replace(/\D/g, '').length
  259.             // }
  260.             new_value = round_number(new_value, 2)
  261.             if(true) {
  262.                 var new_substring =
  263.                       match[0]
  264.                     + ' ['
  265.                     + new_value
  266.                     + " "
  267.                     + new_unit
  268.                     + ']'
  269.             } else {
  270.                 var new_substring =
  271.                       new_value
  272.                     + " "
  273.                     + new_unit
  274.                     + ' ['
  275.                     + match[0]
  276.                     + ']'
  277.             }
  278.  
  279.             text = replaceSubstring(text, match.index, match[0].length, new_substring );
  280.             // Move the matching index past whatever we have replaced.
  281.             // Note: The replacement can be shorter or longer.
  282.             transformationRule.regex.lastIndex = transformationRule.regex.lastIndex + (new_substring.length - match[0].length);
  283.         }
  284.     });
  285.     return text;
  286. };
  287.  
  288. // conversation => convert. Because this has nothing to do with discussions.
  289. // Rounding moved so it happens only immediatly before being inserted into
  290. //  the document.
  291. //  If it's done as the first step we lose a lot(!) of precision.
  292. //  As an example 0.004 inches was rounded to 0, then converted to metric
  293. //  (still 0) and then inserted. Extremely wrong.
  294. //  Also 0.01497 miles gets rounded to 0.01 (33% less!) and then converted to
  295. //  metric. The result is 0.01 * 1.609344 = 0.01609344, way more digits than
  296. //  before we started! This looks extremely precise with 8 significant digits,
  297. //  but it's actually only 1 since began by completely destroying our initial
  298. //  number. Correct convertion should have the same number of significant
  299. //  digits as the initial number (4). But some numbers, like 1 inch, might
  300. //  actually be exactly 1 inch, or 2.54 cm. Rounding 1 inch to 3 cm seems a
  301. //  bit wrong. It's unusual that 1 inch is written like "1.00 inches" even
  302. //  if that precision is intended. So a flat rounding to 2 decimals after(!)
  303. //  choosing a good prefix (and scaling our number by the prefix) should work
  304. //  for most cases.
  305.  
  306. var handleTextNode = function(textNode) {
  307.     var transformedText = transformText(textNode.nodeValue);
  308.     if(textNode.nodeValue != transformedText)
  309.        textNode.nodeValue = transformedText;
  310. };
  311.  
  312. // Travel the node(s) in a recursive fashion.
  313. var walk = function(node) {
  314.   var child, next;
  315.  
  316.   switch (node.nodeType) {
  317.     case 1:  // Element
  318.     case 9:  // Document
  319.     case 11: // Document fragment
  320.       child = node.firstChild;
  321.       while (child) {
  322.         next = child.nextSibling;
  323.         walk(child);
  324.         child = next;
  325.       }
  326.       break;
  327.     case 3: // Text node
  328.       handleTextNode(node);
  329.       break;
  330.     default:
  331.       break;
  332.   }
  333. };
  334.  
  335. var MutationObserver = (window.MutationObserver || window.WebKitMutationObserver);
  336. var observer = new MutationObserver(function (mutations) {
  337.     mutations.forEach(function (mutation) {
  338.         if(mutation.type == 'childList') {
  339.             for (var i = 0; i < mutation.addedNodes.length; ++i) {
  340.                walk(mutation.addedNodes[i]);
  341.             }
  342.         } else if (mutation.type == 'characterData') {
  343.             handleTextNode(mutation.target);
  344.         }
  345.     });
  346. });
  347.  
  348. observer.observe(document, {
  349.     childList: true,
  350.     characterData: true,
  351.     subtree: true,
  352. });
  353.  
  354. walk(document.body);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement