uweb/searchurl/bml/content/comparecells.js

198 lines
5.7 KiB
JavaScript
Raw Normal View History

2020-03-13 04:48:29 +00:00
/**
* Try to color the cells of comparison tables based on their contents.
*
* @title Compare cells
*/
(function comparecells() {
/* Create a new IFRAME to get a "clean" Window object, so we can use its
* console. Sometimes sites (e.g. Twitter) override console.log and even
* the entire console object. "delete console.log" or "delete console"
* does not always work, and messing with the prototype seemed more
* brittle than this. */
let console = (function () {
let iframe = document.getElementById('xxxJanConsole');
if (!iframe) {
iframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
iframe.id = 'xxxJanConsole';
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
}
return iframe && iframe.contentWindow && iframe.contentWindow.console || {
log: function () {}
};
})();
/**
* Get the text content for the given element.
*/
function getTextFromElement(element) {
/* TODO: take IMG@alt, BUTTON@value etc. into account */
return element.textContent.trim().toLowerCase();
}
/**
* Get a Uint8Array of the SHA-256 bytes for the given string.
*/
async function getSha256Bytes(string) {
try {
if (
typeof crypto === 'object' && typeof crypto.subtle === 'object' && typeof crypto.subtle.digest === 'function'
&& typeof Uint8Array === 'function'
&& typeof TextEncoder === 'function'
) {
return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(string)));
}
} catch (e) {
return null;
}
}
async function getColorsForValue(value) {
/* Cache the calculated values. */
getColorsForValue.cellValuesToRgb = getColorsForValue.cellValuesToRgb || {};
if (!getColorsForValue.cellValuesToRgb[value]) {
let normalizedValue = value.trim().toLowerCase();
let hash;
let yesValues = [
'✔',
'yes',
'ja',
'oui',
'si',
'sí'
];
let noValues = [
'x',
'no',
'nee',
'neen',
'nein',
'non',
'no'
];
if (yesValues.indexOf(normalizedValue) > -1) {
/* Make "Yes" cells green. */
getColorsForValue.cellValuesToRgb[value] = [ 150, 255, 32 ];
} else if (noValues.indexOf(normalizedValue) > -1) {
/* Make "No" cells green. */
getColorsForValue.cellValuesToRgb[value] = [ 238, 32, 32 ];
} else if ((shaBytes = await getSha256Bytes(normalizedValue))) {
/* Give other cells a color based on their contents SHA
* hash to ensure consistent random colors every time. */
getColorsForValue.cellValuesToRgb[value] = [
shaBytes[0],
shaBytes[1],
shaBytes[2]
];
} else {
/* If the SHA hash could not be calculated, just use random
* values. These will change on every execution. */
getColorsForValue.cellValuesToRgb[value] = [
Math.random() * 255,
Math.random() * 255,
Math.random() * 255
];
}
}
/* Calculate/approximate the lightness (tweaked from RGB to HSL) to
* determine whether black or white text is best suited. */
let isLight = 150 < (
getColorsForValue.cellValuesToRgb[value][0] * 0.299
+ getColorsForValue.cellValuesToRgb[value][1] * 0.587
+ getColorsForValue.cellValuesToRgb[value][2] * 0.114
);
return {
backgroundColor: 'rgb(' + getColorsForValue.cellValuesToRgb[value].join(', ') + ')',
color: isLight
? 'black'
: 'white',
textShadow: isLight
? '1px 1px 3px white'
: '1px 1px 3px black'
};
}
/* The main function. */
(function execute(document) {
Array.from(document.querySelectorAll('table')).forEach(table => {
Array.from(table.tBodies).forEach(tBody => {
if (tBody.rows.length < 3) {
console.log('Compare cells: skipping table body ', tBody, ' because it only has ', tBody.rows.length, ' rows');
return;
}
Array.from(tBody.rows).forEach(tr => {
/* Determine the values. */
let cellValues = [];
let uniqueCellValues = new Set();
Array.from(tr.cells).forEach((cell, i) => {
/* Don't take the header cells into account. */
if (cell.tagName.toUpperCase() === 'TH') {
return;
}
/* Assume the first cell is a header cell, even if it is not a TH. */
if (i === 0) {
return;
}
cellValues[i] = getTextFromElement(cell);
uniqueCellValues.add(cellValues[i]);
});
/* Color (or not) the cells based on the values. */
let isFirstValue = true;
let firstValue;
cellValues.forEach(async function(cellValue, i) {
let hasTwoUniqueValues = uniqueCellValues.size == 2;
if (isFirstValue) {
firstValue = cellValue;
isFirstValue = false;
}
let backgroundColor;
let color;
let textShadow;
if (
uniqueCellValues.size == 1 ||
(hasTwoUniqueValues && cellValue === firstValue) ||
cellValue.trim() === ''
) {
backgroundColor = 'inherit';
color = 'inherit';
textShadow = 'inherit';
} else {
backgroundColor = (await getColorsForValue(cellValue)).backgroundColor;
color = (await getColorsForValue(cellValue)).color;
textShadow = (await getColorsForValue(cellValue)).textShadow;
}
tr.cells[i].style.setProperty('background-color', backgroundColor, 'important');
tr.cells[i].style.setProperty('color', color, 'important');
tr.cells[i].style.setProperty('text-shadow', textShadow, 'important');
});
});
});
});
/* Recurse for frames and iframes. */
try {
Array.from(document.querySelectorAll('frame, iframe, object[type^="text/html"], object[type^="application/xhtml+xml"]')).forEach(function (elem) {
execute(elem.contentDocument);
});
} catch (e) {
/* Catch exceptions for out-of-domain access, but do not do anything with them. */
}
})(document);
})();