update search engines
[uweb.git] / zh / searchurl / bml / content / comparecells.js
bloba8fe372680fc0c8808afa4183e5548ec48dedca9
1 /**
2  * Try to color the cells of comparison tables based on their contents.
3  *
4  * @title Compare cells
5  */
6 (function comparecells() {
7         /* Create a new IFRAME to get a "clean" Window object, so we can use its
8          * console. Sometimes sites (e.g. Twitter) override console.log and even
9          * the entire console object. "delete console.log" or "delete console"
10          * does not always work, and messing with the prototype seemed more
11          * brittle than this. */
12         let console = (function () {
13                 let iframe = document.getElementById('xxxJanConsole');
14                 if (!iframe) {
15                         iframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
16                         iframe.id = 'xxxJanConsole';
17                         iframe.style.display = 'none';
19                         (document.body || document.documentElement).appendChild(iframe);
20                 }
22                 return iframe && iframe.contentWindow && iframe.contentWindow.console || {
23                         log: function () {}
24                 };
25         })();
27         /**
28          * Get the text content for the given element.
29          */
30         function getTextFromElement(element) {
31                 /* TODO: take IMG@alt, BUTTON@value etc. into account */
32                 return element.textContent.trim().toLowerCase();
33         }
35         /**
36          * Get a Uint8Array of the SHA-256 bytes for the given string.
37          */
38         async function getSha256Bytes(string) {
39                 try {
40                         if (
41                                 typeof crypto === 'object' && typeof crypto.subtle === 'object' && typeof crypto.subtle.digest === 'function'
42                                 && typeof Uint8Array === 'function'
43                                 && typeof TextEncoder === 'function'
44                         ) {
45                                 return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(string)));
46                         }
47                 } catch (e) {
48                         return null;
49                 }
50         }
52         async function getColorsForValue(value) {
53                 /* Cache the calculated values. */
54                 getColorsForValue.cellValuesToRgb = getColorsForValue.cellValuesToRgb || {};
56                 if (!getColorsForValue.cellValuesToRgb[value]) {
57                         let normalizedValue = value.trim().toLowerCase();
58                         let hash;
60                         let yesValues = [
61                                 '✔',
62                                 'yes',
63                                 'ja',
64                                 'oui',
65                                 'si',
66                                 'sí'
67                         ];
69                         let noValues = [
70                                 'x',
71                                 'no',
72                                 'nee',
73                                 'neen',
74                                 'nein',
75                                 'non',
76                                 'no'
77                         ];
79                         if (yesValues.indexOf(normalizedValue) > -1) {
80                                 /* Make "Yes" cells green. */
81                                 getColorsForValue.cellValuesToRgb[value] = [ 150, 255, 32 ];
82                         } else if (noValues.indexOf(normalizedValue) > -1) {
83                                 /* Make "No" cells green. */
84                                 getColorsForValue.cellValuesToRgb[value] = [ 238, 32, 32 ];
85                         } else if ((shaBytes = await getSha256Bytes(normalizedValue))) {
86                                 /* Give other cells a color based on their content’s SHA
87                                  * hash to ensure “consistent random colors” every time. */
88                                 getColorsForValue.cellValuesToRgb[value] = [
89                                         shaBytes[0],
90                                         shaBytes[1],
91                                         shaBytes[2]
92                                 ];
93                         } else {
94                                 /* If the SHA hash could not be calculated, just use random
95                                  * values. These will change on every execution. */
96                                 getColorsForValue.cellValuesToRgb[value] = [
97                                         Math.random() * 255,
98                                         Math.random() * 255,
99                                         Math.random() * 255
100                                 ];
101                         }
102                 }
104                 /* Calculate/approximate the lightness (tweaked from “RGB to HSL”) to
105                  * determine whether black or white text is best suited. */
106                 let isLight = 150 < (
107                         getColorsForValue.cellValuesToRgb[value][0] * 0.299
108                         + getColorsForValue.cellValuesToRgb[value][1] * 0.587
109                         + getColorsForValue.cellValuesToRgb[value][2] * 0.114
110                 );
112                 return {
113                         backgroundColor: 'rgb(' + getColorsForValue.cellValuesToRgb[value].join(', ') + ')',
114                         color: isLight
115                                 ? 'black'
116                                 : 'white',
117                         textShadow: isLight
118                                 ? '1px 1px 3px white'
119                                 : '1px 1px 3px black'
120                 };
121         }
123         /* The main function. */
124         (function execute(document) {
125                 Array.from(document.querySelectorAll('table')).forEach(table => {
126                         Array.from(table.tBodies).forEach(tBody => {
127                                 if (tBody.rows.length < 3) {
128                                         console.log('Compare cells: skipping table body ', tBody, ' because it only has ', tBody.rows.length, ' rows');
129                                         return;
130                                 }
132                                 Array.from(tBody.rows).forEach(tr => {
133                                         /* Determine the values. */
134                                         let cellValues = [];
135                                         let uniqueCellValues = new Set();
137                                         Array.from(tr.cells).forEach((cell, i) => {
138                                                 /* Don't take the header cells into account. */
139                                                 if (cell.tagName.toUpperCase() === 'TH') {
140                                                         return;
141                                                 }
143                                                 /* Assume the first cell is a header cell, even if it is not a TH. */
144                                                 if (i === 0) {
145                                                         return;
146                                                 }
148                                                 cellValues[i] = getTextFromElement(cell);
149                                                 uniqueCellValues.add(cellValues[i]);
150                                         });
152                                         /* Color (or not) the cells based on the values. */
153                                         let isFirstValue = true;
154                                         let firstValue;
155                                         cellValues.forEach(async function(cellValue, i) {
156                                                 let hasTwoUniqueValues = uniqueCellValues.size == 2;
157                                                 if (isFirstValue) {
158                                                         firstValue = cellValue;
159                                                         isFirstValue = false;
160                                                 }
162                                                 let backgroundColor;
163                                                 let color;
164                                                 let textShadow;
166                                                 if (
167                                                         uniqueCellValues.size == 1 ||
168                                                         (hasTwoUniqueValues && cellValue === firstValue) ||
169                                                         cellValue.trim() === ''
170                                                 ) {
171                                                         backgroundColor = 'inherit';
172                                                         color = 'inherit';
173                                                         textShadow = 'inherit';
174                                                 } else {
175                                                         backgroundColor = (await getColorsForValue(cellValue)).backgroundColor;
176                                                         color = (await getColorsForValue(cellValue)).color;
177                                                         textShadow = (await getColorsForValue(cellValue)).textShadow;
178                                                 }
180                                                 tr.cells[i].style.setProperty('background-color', backgroundColor, 'important');
181                                                 tr.cells[i].style.setProperty('color', color, 'important');
182                                                 tr.cells[i].style.setProperty('text-shadow', textShadow, 'important');
183                                         });
184                                 });
185                         });
186                 });
188                 /* Recurse for frames and iframes. */
189                 try {
190                         Array.from(document.querySelectorAll('frame, iframe, object[type^="text/html"], object[type^="application/xhtml+xml"]')).forEach(function (elem) {
191                                 execute(elem.contentDocument);
192                         });
193                 } catch (e) {
194                         /* Catch exceptions for out-of-domain access, but do not do anything with them. */
195                 }
196         })(document);
197 })();