2020-04-29 08:37:43 +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 content’s 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);
|
|
|
|
|
})();
|