unicode-input-toolconverter 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.babelrc.json +3 -0
  2. package/.editorconfig +16 -0
  3. package/.nojekyll +0 -0
  4. package/.nyc_output/out.json +173804 -0
  5. package/.nyc_output/processinfo/index.json +1 -0
  6. package/CHANGES.md +11 -0
  7. package/COPYING.LESSER.txt +180 -0
  8. package/COPYING.txt +687 -0
  9. package/README.md +65 -0
  10. package/_locales/en-US/messages.json +3126 -0
  11. package/_locales/hu-HU/messages.json +3126 -0
  12. package/_locales/pt-BR/messages.json +3126 -0
  13. package/_locales/sv-SE/messages.json +3126 -0
  14. package/babel.config.json +5 -0
  15. package/browser_action/characterSelection.js +91 -0
  16. package/browser_action/charrefConverters.js +389 -0
  17. package/browser_action/chartBuild.js +155 -0
  18. package/browser_action/encodingBehaviors.js +72 -0
  19. package/browser_action/encodings.js +97 -0
  20. package/browser_action/entities.js +12 -0
  21. package/browser_action/entityBehaviors.js +96 -0
  22. package/browser_action/index-es.html +15 -0
  23. package/browser_action/index-instrumented.html +16 -0
  24. package/browser_action/index-pages.html +16 -0
  25. package/browser_action/index.html +16 -0
  26. package/browser_action/index.iife.min.js +2 -0
  27. package/browser_action/index.iife.min.js.map +1 -0
  28. package/browser_action/index.instrumented.iife.min.js +2 -0
  29. package/browser_action/index.instrumented.iife.min.js.map +1 -0
  30. package/browser_action/index.js +95 -0
  31. package/browser_action/preferences/prefDefaults.js +52 -0
  32. package/browser_action/service-worker/sw-activateCallback.js +53 -0
  33. package/browser_action/service-worker/sw-locales.json +6 -0
  34. package/browser_action/service-worker/sw-resources.json +73 -0
  35. package/browser_action/service-worker/sw-unicode-data.json +3 -0
  36. package/browser_action/styles/unicode-dialog.css +391 -0
  37. package/browser_action/templateUtils/elements.js +12 -0
  38. package/browser_action/templateUtils/fill.js +3 -0
  39. package/browser_action/templateUtils/validation.js +6 -0
  40. package/browser_action/templates/chartBuild.js +185 -0
  41. package/browser_action/templates/index.js +1304 -0
  42. package/browser_action/templatesElementCustomization/widgets.js +56 -0
  43. package/browser_action/unicode/UnicodeConverter.js +793 -0
  44. package/browser_action/unicode/charrefunicodeDb.js +258 -0
  45. package/browser_action/unicode/getScriptInfoForCodePoint.js +646 -0
  46. package/browser_action/unicode/hangul.js +253 -0
  47. package/browser_action/unicode/lastScriptNames.json +5 -0
  48. package/browser_action/unicode/parseUnihanFromTextFileStrings.js +56 -0
  49. package/browser_action/unicode/unicodeFieldInfo.js +120 -0
  50. package/browser_action/unicode/unicodeScripts.js +4912 -0
  51. package/browser_action/unicode/unihan.js +129 -0
  52. package/browser_action/unicode/unihanDbPopulate.js +26 -0
  53. package/browser_action/unicodecharref.js +1480 -0
  54. package/browser_action/utils/DOMUtils.js +122 -0
  55. package/browser_action/utils/FetchUtils.js +19 -0
  56. package/browser_action/utils/TextUtils.js +28 -0
  57. package/browser_action/utils/TypedArrayUtils.js +21 -0
  58. package/browser_action/utils/semicolonSeparatedToArray.js +12 -0
  59. package/browser_action/utils/setupServiceWorker.js +193 -0
  60. package/cypress.config.js +21 -0
  61. package/download/entities/copyright-software-20021231.html +70 -0
  62. package/download/entities/html5-uppercase.ent +54 -0
  63. package/download/entities/htmlmathml-f.ent +2164 -0
  64. package/download/entities/isoamsa.ent +201 -0
  65. package/download/entities/isoamsb.ent +177 -0
  66. package/download/entities/isoamsc.ent +77 -0
  67. package/download/entities/isoamsn.ent +148 -0
  68. package/download/entities/isoamso.ent +107 -0
  69. package/download/entities/isoamsr.ent +238 -0
  70. package/download/entities/isobox.ent +95 -0
  71. package/download/entities/isocyr1.ent +122 -0
  72. package/download/entities/isocyr2.ent +81 -0
  73. package/download/entities/isodia.ent +69 -0
  74. package/download/entities/isogrk1.ent +104 -0
  75. package/download/entities/isogrk2.ent +75 -0
  76. package/download/entities/isogrk3.ent +98 -0
  77. package/download/entities/isogrk4.ent +98 -0
  78. package/download/entities/isolat1.ent +117 -0
  79. package/download/entities/isolat2.ent +176 -0
  80. package/download/entities/isomfrk.ent +107 -0
  81. package/download/entities/isomopf.ent +81 -0
  82. package/download/entities/isomscr.ent +107 -0
  83. package/download/entities/isonum.ent +131 -0
  84. package/download/entities/isopub.ent +140 -0
  85. package/download/entities/isotech.ent +216 -0
  86. package/download/entities/mmlalias.ent +598 -0
  87. package/download/entities/mmlextra.ent +154 -0
  88. package/download/entities/predefined.ent +52 -0
  89. package/download/entities/w3centities-f.ent +2276 -0
  90. package/download/entities/xhtml1-lat1.ent +143 -0
  91. package/download/entities/xhtml1-special.ent +78 -0
  92. package/download/entities/xhtml1-symbol.ent +171 -0
  93. package/eslint.config.js +66 -0
  94. package/icons/openWindow16.png +0 -0
  95. package/icons/openWindow24.png +0 -0
  96. package/lgtm.yml +5 -0
  97. package/lib/background.html +11 -0
  98. package/lib/background.js +13 -0
  99. package/lib/overlay.css +6 -0
  100. package/manifest.json +37 -0
  101. package/package.json +121 -0
  102. package/polyfills/browser-polyfill.min.js +8 -0
  103. package/polyfills/browser-polyfill.min.js.map +1 -0
  104. package/server.js +61 -0
  105. package/sw.js +259 -0
  106. package/tools/entities-import.js +62 -0
  107. package/tools/findEsResources.js +30 -0
  108. package/tools/list-locales.js +17 -0
  109. package/tools/parseUnicodeCharts.js +428 -0
  110. package/tools/ucd-import.js +24 -0
  111. package/tools/unicode-charts.html +596 -0
  112. package/tools/unihan-import.js +72 -0
  113. package/unicode_copyright.txt +46 -0
  114. package/vendor/camelcase/index.js +110 -0
  115. package/vendor/fflate/esm/browser.js +2665 -0
  116. package/vendor/intl-dom/dist/index.esm.js +2962 -0
  117. package/vendor/jamilih/dist/jml-es.js +2359 -0
  118. package/vendor/jquery/dist/jquery.js +10716 -0
  119. package/vendor/json-6/dist/index.mjs +1783 -0
  120. package/vendor/miller-columns/dist/index-es.min.js +1 -0
  121. package/vendor/miller-columns/miller-columns.css +75 -0
  122. package/vendor/simple-prefs/dist/index.esm.js +286 -0
  123. package/web-ext-config.cjs +13 -0
@@ -0,0 +1,793 @@
1
+ /* eslint-disable class-methods-use-this -- Todo: fix later */
2
+ import {getUnicodeDefaults} from '../preferences/prefDefaults.js';
3
+ import {getHangulName, getHangulFromName} from './hangul.js';
4
+ import charrefunicodeDb from './charrefunicodeDb.js';
5
+ import unicodecharref from '../unicodecharref.js';
6
+ import camelCase from '../../vendor/camelcase/index.js';
7
+
8
+ /**
9
+ * @typedef {"php"|"css"|"javascript"} UnicodeEscapeMode
10
+ */
11
+
12
+ /**
13
+ * @namespace Converts from one string form to another
14
+ */
15
+ const decim = /&#(\d*);/gu;
16
+ const hexadec = /&#[xX]([\da-fA-F]*);/gu;
17
+
18
+ const builtinEntities = new Set(['apos', 'quot', 'lt', 'gt', 'amp']);
19
+
20
+ // Per https://www.w3.org/TR/xml/#sec-suggested-names
21
+ // Continue also needs: "Characters for Natural Language Identifiers" in
22
+ // https://unicode.org/reports/tr31/ ;
23
+ // Currently appears to be Tables 3, 3a, and 3b
24
+ // (besides \u0027 and \u2019 per XML)
25
+ const xmlName = /[\p{ID_Start}_][\p{ID_Continue}\u0024\u005F\u002D\u002E\u003A\u00B7\u058A\u05F4\u0F0B\u200C\u2010\u2027\u30A0\u30FB\u05F3\u200D]*/gui;
26
+ // const htmlOrXmlEnt = /&([a-z\d]+);/gui; // Works for basic HTML entitites
27
+ const htmlOrXmlEnt = new RegExp('&(' + xmlName.source + ');', 'gui');
28
+
29
+ export const getUnicodeConverter = () => {
30
+ const {getPref} = getUnicodeDefaults();
31
+
32
+ /**
33
+ *
34
+ */
35
+ return class UnicodeConverter {
36
+ /**
37
+ * @param {IntlDom} _
38
+ */
39
+ constructor ({_}) {
40
+ this._ = _;
41
+ this.newents = [];
42
+ this.newcharrefs = [];
43
+
44
+ this.entities = [];
45
+ this.numericCharacterReferences = [];
46
+ }
47
+
48
+ /**
49
+ * Ensure dynamic as array may change.
50
+ * @returns {Integer}
51
+ */
52
+ getAposPos () {
53
+ return this.numericCharacterReferences.indexOf(39);
54
+ }
55
+ /**
56
+ * Ensure dynamic as array may change.
57
+ * @returns {Integer}
58
+ */
59
+ getAmpPos () {
60
+ return this.numericCharacterReferences.indexOf(38);
61
+ }
62
+
63
+ /**
64
+ * @param {string} out
65
+ * @returns {string}
66
+ */
67
+ charref2unicodeval (out) {
68
+ out = out.replaceAll(decim, function (match, match1) {
69
+ return String.fromCodePoint(match1);
70
+ }).replaceAll(hexadec, function (match, match1) {
71
+ return String.fromCodePoint(Number.parseInt(match1, 16));
72
+ });
73
+ return out;
74
+ }
75
+
76
+ /**
77
+ * @param {string} out
78
+ * @returns {string}
79
+ */
80
+ async charref2htmlentsval (out) {
81
+ // If true, should allow conversion to '
82
+ const xhtmlentmode = await getPref('xhtmlentmode');
83
+
84
+ out = out.replaceAll(decim, (match, match1) => {
85
+ const matched = this.numericCharacterReferences.indexOf(
86
+ Number.parseInt(match1)
87
+ );
88
+ if (
89
+ matched !== -1 &&
90
+ (matched !== this.getAposPos() || xhtmlentmode)
91
+ ) {
92
+ return '&' + this.entities[matched] + ';';
93
+ }
94
+ return match;
95
+ }).replaceAll(hexadec, (match, match1) => {
96
+ const matched = this.numericCharacterReferences.indexOf(
97
+ Number.parseInt('0x' + match1, 16)
98
+ );
99
+ if (
100
+ matched !== -1 && (
101
+ matched !== this.getAposPos() || xhtmlentmode
102
+ )
103
+ ) {
104
+ return '&' + this.entities[matched] + ';';
105
+ }
106
+ return match;
107
+ });
108
+ return out;
109
+ }
110
+
111
+ /**
112
+ * @param {string} unicodeToConvert
113
+ * @param {boolean} leaveSurrogates
114
+ * @returns {string}
115
+ */
116
+ async unicode2charrefDecval (unicodeToConvert, leaveSurrogates) {
117
+ let out = '';
118
+ const asciiLt128 = await getPref('asciiLt128');
119
+ for (let i = 0; i < unicodeToConvert.length; i++) {
120
+ let temp = unicodeToConvert.charCodeAt(i);
121
+ // Todo: Redo with `codePointAt`?
122
+ if (!leaveSurrogates && temp >= 0xD800 && temp < 0xF900) { // surrogate
123
+ temp = ((temp - 0xD800) * 0x400) +
124
+ (unicodeToConvert.charCodeAt(i + 1) - 0xDC00) + 0x10000;
125
+ // Could do test on temp.isNan() (e.g., if trying to convert
126
+ // a surrogate by itself in regular (non-surrogate converting) mode)
127
+ out += '&#' + temp + ';';
128
+ i++; // Skip the next surrogate
129
+
130
+ // Replace this 'if' condition and remove the 'else' if also
131
+ // want ascii
132
+ } else if (temp >= 128 || asciiLt128) {
133
+ out += '&#' + temp + ';';
134
+ } else {
135
+ out += unicodeToConvert.charAt(i);
136
+ }
137
+ }
138
+ return out;
139
+ }
140
+
141
+ /**
142
+ * @param {string} unicodeToConvert
143
+ * @param {boolean} leaveSurrogates
144
+ * @param {UnicodeEscapeMode} type
145
+ * @returns {string}
146
+ */
147
+ async unicode2charrefHexval (unicodeToConvert, leaveSurrogates, type) {
148
+ // alert(unicodeToConvert + '::' + leaveSurrogates + '::' + type);
149
+ let out = '';
150
+ let xstyle, beginEscape, endEscape, cssUnambiguous;
151
+ // Fix: offer a U+.... option (similar to 'php' or 'javascript'
152
+ // depending on if length is desired as 4 or 6)
153
+ if (type === 'javascript' || type === 'php') {
154
+ xstyle = '';
155
+ beginEscape = String.raw`\u`;
156
+ endEscape = '';
157
+ } else if (type === 'css') {
158
+ cssUnambiguous = await getPref('cssUnambiguous');
159
+ xstyle = '';
160
+ beginEscape = '\\';
161
+ endEscape = cssUnambiguous ? '' : await getPref('cssWhitespace');
162
+ } else {
163
+ xstyle = 'x';
164
+ beginEscape = '&#';
165
+ endEscape = ';';
166
+ /*
167
+ if (!(await getPref('hexstyleLwr'))) {
168
+ xstyle = 'X';
169
+ }
170
+ */
171
+ }
172
+
173
+ const [hexLettersUpper, asciiLt128] = await Promise.all([
174
+ getPref('hexLettersUpper'),
175
+ getPref('asciiLt128')
176
+ ]);
177
+
178
+ for (let i = 0; i < unicodeToConvert.length; i++) {
179
+ let hexletters;
180
+ let temp = unicodeToConvert.charCodeAt(i);
181
+ // Todo: Redo with `codePointAt`?
182
+ if (!leaveSurrogates && temp >= 0xD800 && temp < 0xF900) { // surrogate
183
+ temp = ((temp - 0xD800) * 0x400) +
184
+ (unicodeToConvert.charCodeAt(i + 1) - 0xDC00) + 0x10000;
185
+ hexletters = temp.toString(16);
186
+ i++; // Skip the next surrogate
187
+ if (hexLettersUpper) {
188
+ hexletters = hexletters.toUpperCase();
189
+ }
190
+ if ((type === 'php' || cssUnambiguous) && hexletters.length < 6) {
191
+ hexletters = hexletters.padStart(6, '0');
192
+ }
193
+ out += beginEscape + xstyle + hexletters + endEscape;
194
+ // Replace this 'if' condition and remove the 'else' if also want ascii
195
+ } else if (temp >= 128 || asciiLt128) {
196
+ hexletters = temp.toString(16);
197
+ if (hexLettersUpper) {
198
+ hexletters = hexletters.toUpperCase();
199
+ }
200
+ if (type === 'javascript' && hexletters.length < 4) {
201
+ hexletters = hexletters.padStart(4, '0');
202
+ } else if (
203
+ (type === 'php' || cssUnambiguous) &&
204
+ hexletters.length < 6
205
+ ) {
206
+ hexletters = hexletters.padStart(6, '0');
207
+ }
208
+ out += beginEscape + xstyle + hexletters + endEscape;
209
+ } else {
210
+ out += unicodeToConvert.charAt(i);
211
+ }
212
+ }
213
+ return out;
214
+ }
215
+
216
+ /**
217
+ * @param {string} unicodeToConvert
218
+ * @returns {string}
219
+ */
220
+ async unicode2htmlentsval (unicodeToConvert) {
221
+ for (let i = 0; i < this.newents.length; i++) {
222
+ unicodeToConvert = unicodeToConvert.replaceAll(
223
+ this.newcharrefs[i], '&' + this.newents[i] + ';'
224
+ );
225
+ }
226
+
227
+ let out = '';
228
+ // If true, should allow conversion to &apos;
229
+ const xhtmlentmode = await getPref('xhtmlentmode');
230
+ // If true, will not convert '&' to '&amp;'
231
+ const ampkeep = await getPref('ampkeep');
232
+
233
+ for (const ch of unicodeToConvert) {
234
+ const codePoint = ch.codePointAt();
235
+ const tempcharref = this.numericCharacterReferences.indexOf(
236
+ codePoint
237
+ );
238
+
239
+ out += tempcharref !== -1 &&
240
+ (xhtmlentmode || tempcharref !== this.getAposPos()) &&
241
+ (!ampkeep || tempcharref !== this.getAmpPos())
242
+ ? '&' + this.entities[tempcharref] + ';'
243
+ : ch;
244
+ }
245
+ return out;
246
+ }
247
+
248
+ /**
249
+ * @param {string} out
250
+ * @returns {string}
251
+ */
252
+ async htmlents2charrefDecval (out) {
253
+ // If true, don't convert &apos;, &quot;, &lt;, &gt;, and &amp;
254
+ const xmlentkeep = await getPref('xmlentkeep');
255
+ return out.replaceAll(htmlOrXmlEnt, (match, match1) => {
256
+ if (!xmlentkeep || !builtinEntities.has(match1)) {
257
+ // If recognized multiple char ent. (won't convert these to decimal)
258
+ if (this.newents.includes(match1)) {
259
+ return this.newcharrefs[this.newents.indexOf(match1)];
260
+ }
261
+ // If recognized single char. ent.
262
+ if (this.entities.includes(match1)) {
263
+ return '&#' + this.numericCharacterReferences[
264
+ this.entities.indexOf(match1)
265
+ ] + ';';
266
+ }
267
+ // If unrecognized
268
+ return '&' + match1 + ';';
269
+ }
270
+ // If keeping predefined XML entities (and this is one)
271
+ return '&' + match1 + ';';
272
+ });
273
+ }
274
+
275
+ /**
276
+ * @param {string} out
277
+ * @returns {string}
278
+ */
279
+ async htmlents2charrefHexval (out) {
280
+ const xstyle = 'x';
281
+ /* if (!(await getPref('hexstyleLwr'))) {
282
+ xstyle = 'X';
283
+ } */
284
+ // If true, don't convert &apos;, &quot;, &lt;, &gt;, and &amp;
285
+ const [xmlentkeep, hexLettersUpper] = await Promise.all([
286
+ getPref('xmlentkeep'),
287
+ getPref('hexLettersUpper')
288
+ ]);
289
+ return out.replaceAll(htmlOrXmlEnt, (match, match1) => {
290
+ if (!xmlentkeep || !builtinEntities.has(match1)) {
291
+ const b = this.numericCharacterReferences[
292
+ this.entities.indexOf(match1)
293
+ ];
294
+ const c = this.newents.indexOf(match1);
295
+
296
+ // If recognized multiple char. ent. (won't convert these to
297
+ // hexadecimal)
298
+ if (c !== -1) {
299
+ return this.newcharrefs[c];
300
+ }
301
+
302
+ // If recognized single char. ent.
303
+ if (this.entities.includes(match1)) {
304
+ let hexletters = b.toString(16);
305
+ if (hexLettersUpper) {
306
+ hexletters = hexletters.toUpperCase();
307
+ }
308
+ return '&#' + xstyle + hexletters + ';';
309
+ } // If unrecognized ent.
310
+ return '&' + match1 + ';';
311
+ }
312
+ // If keeping predefined XML entities (and this is one)
313
+ return '&' + match1 + ';';
314
+ });
315
+ }
316
+
317
+ /**
318
+ * @param {string} out
319
+ * @returns {string}
320
+ */
321
+ async htmlents2unicodeval (out) {
322
+ // If true, don't convert &apos;, &quot;, &lt;, &gt;, and &amp;
323
+ const xmlentkeep = await getPref('xmlentkeep');
324
+ return out.replaceAll(htmlOrXmlEnt, (match, match1) => {
325
+ if (!xmlentkeep || !builtinEntities.has(match1)) {
326
+ const b = this.numericCharacterReferences[
327
+ this.entities.indexOf(match1)
328
+ ];
329
+
330
+ // If recognized multiple char ent.
331
+ if (this.newents.includes(match1)) {
332
+ return this.newcharrefs[this.newents.indexOf(match1)];
333
+ }
334
+ // If recognized single char. ent.
335
+ if (this.entities.includes(match1)) {
336
+ return String.fromCodePoint(b);
337
+ }
338
+ // If unrecognized
339
+ return '&' + match1 + ';';
340
+ }
341
+ // If keeping predefined XML entities (and this is one)
342
+ return '&' + match1 + ';';
343
+ });
344
+ }
345
+
346
+ /**
347
+ * @param {string} out
348
+ * @returns {string}
349
+ */
350
+ hex2decval (out) {
351
+ return out.replaceAll(hexadec, function (match, match1) {
352
+ return '&#' + Number.parseInt(match1, 16) + ';';
353
+ });
354
+ }
355
+
356
+ /**
357
+ * @param {string} out
358
+ * @returns {string}
359
+ */
360
+ async dec2hexval (out) {
361
+ const xstyle = 'x';
362
+ /* if (!(await getPref('hexstyleLwr'))) {
363
+ xstyle = 'X';
364
+ } */
365
+ const hexLettersUpper = await getPref('hexLettersUpper');
366
+ return out.replaceAll(decim, function (match, match1) {
367
+ let hexletters = Number(match1).toString(16);
368
+ if (hexLettersUpper) {
369
+ hexletters = hexletters.toUpperCase();
370
+ }
371
+ return '&#' + xstyle + hexletters + ';';
372
+ });
373
+ }
374
+
375
+ /**
376
+ * @param {string} toconvert
377
+ * @returns {string}
378
+ */
379
+ cssescape2unicodeval (toconvert) {
380
+ // See:
381
+ // https://www.w3.org/TR/CSS21/syndata.html#characters
382
+ // https://www.w3.org/TR/CSS21/grammar.html
383
+ let unicode = '';
384
+ for (let i = 0; i < toconvert.length; i++) {
385
+ const s = toconvert[i];
386
+ if (s !== '\\') {
387
+ unicode += s;
388
+ continue;
389
+ }
390
+ const next = toconvert[i + 1];
391
+ switch (next) {
392
+ /*
393
+ case '\r':
394
+ if (toconvert[i + 2] === '\n') {
395
+ unicode += s + next + toconvert[i + 2];
396
+ i++; // Skip the extra newline character here
397
+ break;
398
+ }
399
+ // Fallthrough
400
+ */
401
+ case '\n':
402
+ // Fallthrough
403
+ case '\f':
404
+ // Copy as is:
405
+ unicode += s + next;
406
+ break;
407
+ default: {
408
+ const hexEsc = toconvert.slice(i + 1).match(/^([A-Fa-f\d]{1,5})(?:([A-Fa-f\d])|(\r\n|[ \t\r\n\f])?)/u); // 1-5 hex and WS, or 6 hex
409
+ if (hexEsc) {
410
+ i += hexEsc[0].length - 1; // We want to skip the whole structure
411
+ const hex = hexEsc[1] + (hexEsc[2] || ''); // [2] only if is 6-digit
412
+ const dec = Number.parseInt(hex, 16);
413
+
414
+ // \u000 is disallowed in CSS 2.1 (behavior undefined) and above
415
+ // 0x10FFFF is beyond valid Unicode; fix: disallow non-characters
416
+ // too?
417
+ if (dec > 0x10FFFF || dec === 0) {
418
+ // Replacement character since not valid Unicode
419
+ unicode += '\uFFFD';
420
+ break;
421
+ }
422
+
423
+ const hexStr = String.fromCodePoint(dec);
424
+
425
+ // Too low ASCII to be converted (not a letter, digit,
426
+ // underscore, or hyphen)
427
+ // eslint-disable-next-line unicorn/prefer-ternary -- Structure
428
+ if (dec < 0xA1 && (/[^\w-]/u).test(hexStr)) {
429
+ // Don't convert since won't be valid if unescaped
430
+ // Although https://www.w3.org/TR/CSS21/grammar.html#scanner
431
+ // (under "nonascii" which is a possible (indirect) component
432
+ // of identifiers) seems to permit any non-ASCII equal to or
433
+ // above 0x80 (decimal 128), per https://www.w3.org/TR/CSS21/syndata.html#characters
434
+ // only non-escaped characters above 0xA1 are permitted
435
+ // (limitation of Flex scanner based in Latin?); testing in
436
+ // Firefox also shows values lower than 0xA1 in CSS do not work
437
+ // there unless escaped
438
+ unicode += s + hexEsc[0];
439
+ // If begins with a digit or hyphen and digit, might not be valid
440
+ // if unescaped (if at beginning of identifier) so don't
441
+ // convert (if followed by an escaped number, there is no concern
442
+ // it will be avoided here, since the escaped number will remain
443
+ // escaped on the next iteration (by this same condition)
444
+ // Reenable if know what this is supposed to be!
445
+ /* } else if ((/^-?\d/u).test(hexStr + toconvert[i + 2])) {
446
+ unicode += s + hexEsc[0]; */
447
+ } else {
448
+ unicode += hexStr;
449
+ }
450
+ } else {
451
+ // [^\r\n\f0-9a-f] // May be escaping something that needs
452
+ // to be escaped for CSS grammar, so keep
453
+ unicode += s + next;
454
+ }
455
+ break;
456
+ }
457
+ }
458
+ i++;
459
+ }
460
+ return unicode;
461
+ }
462
+
463
+ /**
464
+ * @param {string} toconvert
465
+ * @param {UnicodeEscapeMode} mode
466
+ * @returns {string}
467
+ */
468
+ jsescape2unicodeval (toconvert, mode) {
469
+ let unicode = '', hexChrs;
470
+ for (let i = 0; i < toconvert.length; i++) {
471
+ const s = toconvert[i];
472
+ if (s !== '\\') {
473
+ unicode += s;
474
+ } else {
475
+ const next = toconvert[i + 1];
476
+ if (mode === 'php') {
477
+ switch (next) {
478
+ case '\\': // Just add one backslash
479
+ unicode += s;
480
+ break;
481
+ case 'u':
482
+ hexChrs = (/^[a-fA-F\d]{6}|[a-fA-F\d]{4}/u).exec(toconvert.slice(i + 2));
483
+ if (hexChrs) {
484
+ unicode += String.fromCodePoint(
485
+ Number.parseInt(hexChrs[0], 16)
486
+ );
487
+ i += hexChrs[0].length; // 4 or 6
488
+ break;
489
+ }
490
+ // Fallthrough
491
+ default:
492
+ unicode += s + next;
493
+ break;
494
+ }
495
+ } else {
496
+ switch (next) {
497
+ case '\\': // Just add one backslash
498
+ unicode += s;
499
+ break;
500
+ case 'r':
501
+ unicode += '\u000D';
502
+ break;
503
+ case 'n':
504
+ unicode += '\n';
505
+ break;
506
+ case 't':
507
+ unicode += '\t';
508
+ break;
509
+ case 'f':
510
+ unicode += '\f';
511
+ break;
512
+ case 'v':
513
+ unicode += '\v';
514
+ break;
515
+ case 'b':
516
+ unicode += '\b';
517
+ break;
518
+ case 'u':
519
+ hexChrs = (/^[a-fA-F\d]{4}/u).exec(toconvert.slice(i + 2));
520
+ if (hexChrs) {
521
+ unicode += String.fromCharCode(Number.parseInt(hexChrs[0], 16));
522
+ i += hexChrs[0].length; // 4
523
+ break;
524
+ }
525
+ // Fallthrough
526
+ default: // Unrecognized escape, so just add both characters
527
+ unicode += s + next;
528
+ break;
529
+ }
530
+ }
531
+ i++;
532
+ }
533
+ }
534
+ return unicode;
535
+ }
536
+
537
+ /**
538
+ * @param {string} toconvert
539
+ * @returns {string}
540
+ */
541
+ async unicode2jsescapeval (toconvert) {
542
+ return await this.unicode2charrefHexval(toconvert, true, 'javascript');
543
+ }
544
+
545
+ /**
546
+ * @param {string} toconvert
547
+ * @returns {string}
548
+ */
549
+ async unicodeTo6DigitVal (toconvert) {
550
+ return await this.unicode2charrefHexval(toconvert, false, 'php');
551
+ }
552
+
553
+ /**
554
+ * @param {string} toconvert
555
+ * @returns {string}
556
+ */
557
+ async unicode2cssescapeval (toconvert) {
558
+ return await this.unicode2charrefHexval(toconvert, false, 'css');
559
+ }
560
+
561
+ /**
562
+ * @param {string} toconvert
563
+ * @returns {string}
564
+ */
565
+ sixDigit2UnicodeVal (toconvert) {
566
+ return this.jsescape2unicodeval(toconvert, 'php');
567
+ }
568
+
569
+ // Todo: Move these to their own database-driven file?
570
+
571
+ /**
572
+ * @param {string} toconvert
573
+ * @returns {string}
574
+ */
575
+ async unicode2CharDescVal (toconvert) {
576
+ const asciiLt128 = await getPref('asciiLt128');
577
+ return (await Promise.all([...toconvert].map(async (ch) => {
578
+ const codePoint = ch.codePointAt();
579
+ if (codePoint >= 128 || asciiLt128) {
580
+ const charDesc = await this.getCharDescForCodePoint(codePoint);
581
+ if (charDesc) { // Skip if no description in database
582
+ return String.raw`\C{` + charDesc + '}';
583
+ }
584
+ }
585
+ return ch;
586
+ }))).join('');
587
+ }
588
+
589
+ /**
590
+ * @param {string} toconvert
591
+ * @returns {Promise<string>}
592
+ */
593
+ async charDesc2UnicodeVal (toconvert) {
594
+ const promises = [];
595
+ toconvert.replaceAll(/\\C\{([^}]*)\}/gu, (n, n1) => {
596
+ promises.push(this.lookupUnicodeValueByCharName(n1));
597
+ });
598
+
599
+ const unicodeVals = await Promise.all(promises);
600
+
601
+ let i = -1;
602
+ return toconvert.replaceAll(/\\C\{([^}]*)\}/gu, () => {
603
+ ++i;
604
+ return unicodeVals[i]
605
+ ? String.fromCodePoint(unicodeVals[i])
606
+ : '\uFFFD'; // Replacement character if not found?
607
+ });
608
+ }
609
+
610
+ /**
611
+ * Obtain a Unicode character description for a given decimal-expressed
612
+ * code point.
613
+ * @param {Integer} dec The code point of the description to obtain
614
+ * @returns {string} The Unicode character description
615
+ */
616
+ async getCharDescForCodePoint (dec) {
617
+ // Todo: This should support CJK and those which are only marked by
618
+ // ranges (e.g., surrogates, though for these, see calling code)
619
+ try {
620
+ if (dec >= 0xAC00 && dec <= 0xD7A3) {
621
+ return getHangulName(dec);
622
+ }
623
+
624
+ const hexStr = dec.toString(16).toUpperCase().padStart(4, '0');
625
+
626
+ await charrefunicodeDb.connect();
627
+ const {
628
+ name, unicode1Name
629
+ } = await charrefunicodeDb.getUnicodeFields(hexStr);
630
+
631
+ // Todo: Unihan
632
+ /*
633
+ if (!name) {
634
+ }
635
+ */
636
+
637
+ if (unicode1Name && name.includes('<')) {
638
+ return `${unicode1Name} (${name})`;
639
+ }
640
+
641
+ return name;
642
+ /* istanbul ignore next -- Debugging */
643
+ } catch (e) {
644
+ /* istanbul ignore next -- Debugging */
645
+ alert(e);
646
+ }
647
+ /* istanbul ignore next -- Debugging */
648
+ return undefined;
649
+ }
650
+
651
+ /**
652
+ * Search for a Unicode character value matching a given description.
653
+ * @param {string} value
654
+ * @returns {Integer}
655
+ */
656
+ async lookupUnicodeValueByCharName (value) {
657
+ // todo: Character names for Unihan
658
+ const forceUnicode = true;
659
+ const table = forceUnicode
660
+ ? 'UnicodeData'
661
+ /* istanbul ignore next -- Known todo */
662
+ : 'Unihan';
663
+ const id = forceUnicode
664
+ ? 'searchName'
665
+ /* istanbul ignore next -- Known todo */
666
+ : 'searchkDefinition';
667
+ await this.searchUnicode(
668
+ {id, value}, table, 'noChart=true', 'strict=true'
669
+ );
670
+ if (!this.descripts[0] && value.length <= 7) {
671
+ // Try Hangul (if possible size for Hangul)
672
+ // Fix: Is Hangul allowed in PHP 6 Unicode escape names?
673
+ const ret = getHangulFromName(value);
674
+ return ret ? ret.charCodeAt(0) : false;
675
+ }
676
+ return this.descripts[0];
677
+ }
678
+
679
+ // Used for conversions, so included here (also used externally)
680
+ /**
681
+ * @param {{id: string, value: string}} obj E.g., an input element
682
+ * @param {string} table
683
+ * @param {boolean} nochart
684
+ * @param {boolean} strict
685
+ * @returns {Promise<void>}
686
+ */
687
+ async searchUnicode (obj, table, nochart, strict) { // Fix: allow Jamo!
688
+ if (!table) {
689
+ table = 'UnicodeData';
690
+ }
691
+ // const table = 'Unihan'; // fix: determine by pull-down
692
+ const nameDescVal = obj.value;
693
+
694
+ /* istanbul ignore next -- Just a guard */
695
+ if (
696
+ // Don't query the other databases here
697
+ (obj.id.startsWith('searchk') && table === 'UnicodeData') ||
698
+ ((/^search[^k]/u).test(obj.id) && table === 'Unihan')
699
+ ) {
700
+ return;
701
+ }
702
+ const nameDesc = obj.id.replace(/^search/u, '');
703
+
704
+ // const nameDesc = (table === 'Unihan') ? 'kDefinition'
705
+ // : 'Name'; // Fix: let Unihan search Mandarin, etc.
706
+
707
+ const conn = table === 'Unihan'
708
+ ? unicodecharref.unihanDatabase
709
+ : charrefunicodeDb;
710
+
711
+ if (table === 'Unihan' && !nochart && !unicodecharref.unihanDb_exists) {
712
+ alert(this._('need_download_unihan'));
713
+ return;
714
+ }
715
+
716
+ await conn.connect();
717
+ this.descripts = [];
718
+
719
+ try {
720
+ /*
721
+ if (nameDesc === 'General_Category' && nameDescVal === 'Cn') {
722
+ try {
723
+ const chars = await conn.getAll();
724
+ let j = 0;
725
+ for (let i = 0; i < 0x10FFFE; i++) {
726
+ let {name, codePoint} = chars[j++];
727
+ const range = name.endsWith('First>');
728
+ if (range) {
729
+ ({name, codePoint} = chars[j++]);
730
+ const endRange = name.endsWith('Last>');
731
+ if (endRange) {
732
+ i = Number.parseInt(codePoint, 16);
733
+ continue;
734
+ }
735
+ }
736
+ let hex = Number.parseInt(codePoint, 16);
737
+ for (let endHex = hex; i < endHex; i++, hex++) {
738
+ this.descripts.push(i);
739
+ }
740
+ }
741
+ } catch (e) {
742
+ alert(e);
743
+ }
744
+ } else { */
745
+ const field = nameDesc;
746
+ const camelizedField = camelCase(nameDesc);
747
+
748
+ // Todo: Add indexes for each instead and then query with
749
+ // `nameDescVal`, at least for `strict`
750
+ const chars = await conn.getAll();
751
+
752
+ const filteredChars = strict
753
+ ? chars.filter((chr) => {
754
+ const cell = table === 'Unihan'
755
+ /* istanbul ignore next -- Not yet using strict checking */
756
+ ? chr.columns[
757
+ unicodecharref.Unihan.indexOf(field)
758
+ ]
759
+ : chr[camelizedField];
760
+ return cell.toLowerCase() === nameDescVal.toLowerCase();
761
+ })
762
+ : chars.filter((chr) => {
763
+ const cell = table === 'Unihan'
764
+ ? chr.columns[
765
+ unicodecharref.Unihan.indexOf(field)
766
+ ]
767
+ : chr[camelizedField];
768
+ return cell.toLowerCase().includes(
769
+ nameDescVal.toLowerCase()
770
+ );
771
+ });
772
+
773
+ filteredChars.forEach((filteredChar) => {
774
+ const {codePoint} = filteredChar;
775
+ const hex = Number.parseInt(codePoint, 16);
776
+ if (table === 'UnicodeData' &&
777
+ (hex >= 0xF900 && hex < 0xFB00)
778
+ ) { // Don't search for compatibility if searching Unicode
779
+ return;
780
+ }
781
+ // Fix: inefficient, but fits more easily into current pattern
782
+ this.descripts.push(hex);
783
+ });
784
+ /* istanbul ignore next -- Debugging */
785
+ } catch (e) {
786
+ /* istanbul ignore next -- Debugging */
787
+ alert(e);
788
+ } finally {
789
+ // conn.close();
790
+ }
791
+ }
792
+ };
793
+ };