1 | /**
|
2 | * Sutton SignWriting Core Module v1.4.2 (https://github.com/sutton-signwriting/core)
|
3 | * Author: Steve Slevinski (https://SteveSlevinski.me)
|
4 | * swuquery.js is released under the MIT License.
|
5 | */
|
6 |
|
7 | (function (global, factory) {
|
8 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
9 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
10 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.ssw = global.ssw || {}, global.ssw.swuquery = {})));
|
11 | })(this, (function (exports) { 'use strict';
|
12 |
|
13 | /**
|
14 | * Object of regular expressions for SWU query strings
|
15 | *
|
16 | * { base, coord, var, symbol, range, item, list, prefix, signbox, full }
|
17 | * @alias swuquery.re
|
18 | * @type {object}
|
19 | */
|
20 | let re$2 = {
|
21 | 'base': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))',
|
22 | 'coord': '(?:(?:\uD836[\uDC0C-\uDDFF]){2})?',
|
23 | 'var': 'V[0-9]+'
|
24 | };
|
25 | re$2.symbol = `${re$2.base}f?r?`;
|
26 | re$2.range = `R${re$2.base}${re$2.base}`;
|
27 | re$2.item = `(?:${re$2.symbol}|${re$2.range})`;
|
28 | re$2.list = `${re$2.item}(?:o${re$2.item})*`;
|
29 | re$2.prefix = `(?:A(?:${re$2.list})+)?T`;
|
30 | re$2.signbox = `(?:${re$2.list}${re$2.coord})*`;
|
31 | re$2.full = `Q(${re$2.prefix})?(${re$2.signbox})?(${re$2.var})?(-?)`;
|
32 |
|
33 | /**
|
34 | * Object of regular expressions for SWU strings in UTF-16
|
35 | *
|
36 | * @alias swu.re
|
37 | * @property {string} symbol - regular expressions for a symbol
|
38 | * @property {string} coord - regular expressions for a coordinate
|
39 | * @property {string} sort - regular expressions for the sorting marker
|
40 | * @property {string} box - regular expression for a signbox marker
|
41 | * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
|
42 | * @property {string} spatial - regular expression for a symbol followed by a coordinate
|
43 | * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
|
44 | * @property {string} sign - regular expression for an optional prefix followed by a signbox
|
45 | * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
|
46 | */
|
47 | let re$1 = {
|
48 | 'symbol': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))',
|
49 | 'coord': '(?:\uD836[\uDC0C-\uDDFF]){2}',
|
50 | 'sort': '\uD836\uDC00',
|
51 | 'box': '\uD836[\uDC01-\uDC04]'
|
52 | };
|
53 | re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
|
54 | re$1.spatial = `${re$1.symbol}${re$1.coord}`;
|
55 | re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
|
56 | re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
|
57 | re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
|
58 |
|
59 | /** The convert module contains functions to convert between Formal SignWriitng in ASCII (FSW) and SignWriting in Unicode (SWU) characters, along with other types of data.
|
60 | * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
|
61 | * @module convert
|
62 | */
|
63 | /**
|
64 | * Function to convert an SWU number character to an integer
|
65 | * @function convert.swu2num
|
66 | * @param {string} swuNum - SWU number character
|
67 | * @returns {number} Integer value for number
|
68 | * @example
|
69 | * convert.swu2num('𝤆')
|
70 | *
|
71 | * return 500
|
72 | */
|
73 |
|
74 |
|
75 | const swu2num = swuNum => parseInt(swuNum.codePointAt(0)) - 0x1D80C + 250;
|
76 | /**
|
77 | * Function to convert a number to an SWU number character
|
78 | * @function convert.num2swu
|
79 | * @param {number} num - Integer value for number
|
80 | * @returns {string} SWU number character
|
81 | * @example
|
82 | * convert.num2swu(500)
|
83 | *
|
84 | * return '𝤆'
|
85 | */
|
86 |
|
87 |
|
88 | const num2swu = num => String.fromCodePoint(0x1D80C + parseInt(num) - 250);
|
89 | /**
|
90 | * Function to convert two SWU number characters to an array of x,y integers
|
91 | * @function convert.swu2coord
|
92 | * @param {string} swuCoord - Two SWU number character
|
93 | * @returns {number[]} Array of x,y integers
|
94 | * @example
|
95 | * convert.swu2coord('𝤆𝤆')
|
96 | *
|
97 | * return [500, 500]
|
98 | */
|
99 |
|
100 |
|
101 | const swu2coord = swuCoord => [swu2num(swuCoord.slice(0, 2)), swu2num(swuCoord.slice(2, 4))];
|
102 | /**
|
103 | * Function to convert an array of x,y integers to two SWU number characters
|
104 | * @function convert.coord2swu
|
105 | * @param {number[]} coord - Array of x,y integers
|
106 | * @returns {string} Two SWU number character
|
107 | * @example
|
108 | * convert.coord2swu([500, 500])
|
109 | *
|
110 | * return '𝤆𝤆'
|
111 | */
|
112 |
|
113 |
|
114 | const coord2swu = coord => coord.map(num => num2swu(num)).join('');
|
115 | /**
|
116 | * Function to convert an SWU symbol character to a code point on plane 4
|
117 | * @function convert.swu2code
|
118 | * @param {string} swuSym - SWU symbol character
|
119 | * @returns {number} Code point on plane 4
|
120 | * @example
|
121 | * convert.swu2code('')
|
122 | *
|
123 | * return 0x40001
|
124 | */
|
125 |
|
126 |
|
127 | const swu2code = swuSym => parseInt(swuSym.codePointAt(0));
|
128 | /**
|
129 | * Function to convert a code point on plane 4 to an SWU symbol character
|
130 | * @function convert.code2swu
|
131 | * @param {number} code - Code point on plane 4
|
132 | * @returns {string} SWU symbol character
|
133 | * @example
|
134 | * convert.code2swu(0x40001)
|
135 | *
|
136 | * return ''
|
137 | */
|
138 |
|
139 |
|
140 | const code2swu = code => String.fromCodePoint(code);
|
141 | /**
|
142 | * Function to convert an SWU symbol character to an FSW symbol key
|
143 | * @function convert.swu2key
|
144 | * @param {string} swuSym - SWU symbol character
|
145 | * @returns {string} FSW symbol key
|
146 | * @example
|
147 | * convert.swu2key('')
|
148 | *
|
149 | * return 'S10000'
|
150 | */
|
151 |
|
152 |
|
153 | const swu2key = swuSym => {
|
154 | const symcode = swu2code(swuSym) - 0x40001;
|
155 | const base = parseInt(symcode / 96);
|
156 | const fill = parseInt((symcode - base * 96) / 16);
|
157 | const rotation = parseInt(symcode - base * 96 - fill * 16);
|
158 | return 'S' + (base + 0x100).toString(16) + fill.toString(16) + rotation.toString(16);
|
159 | };
|
160 | /**
|
161 | * Function to convert an FSW symbol key to an SWU symbol character
|
162 | * @function convert.key2swu
|
163 | * @param {string} key - FSW symbol key
|
164 | * @returns {string} SWU symbol character
|
165 | * @example
|
166 | * convert.key2swu('S10000')
|
167 | *
|
168 | * return ''
|
169 | */
|
170 |
|
171 |
|
172 | const key2swu = key => code2swu(0x40001 + (parseInt(key.slice(1, 4), 16) - 256) * 96 + parseInt(key.slice(4, 5), 16) * 16 + parseInt(key.slice(5, 6), 16));
|
173 |
|
174 | const parsePrefix = text => {
|
175 | return {
|
176 | required: true,
|
177 | parts: text == 'T' ? undefined : text.match(new RegExp(`(${re$2.list})`, 'g')).map(part => {
|
178 | if (part.includes('o')) {
|
179 | return ['or'].concat(part.match(new RegExp(`(${re$2.item})`, 'g')).map(part => part[0] != 'R' ? part : [part.slice(1, 3), part.slice(3, 5)]));
|
180 | } else {
|
181 | return part[0] != 'R' ? part : [part.slice(1, 3), part.slice(3, 5)];
|
182 | }
|
183 | })
|
184 | };
|
185 | };
|
186 |
|
187 | const parseSignbox = text => {
|
188 | return text.match(new RegExp(`(${re$2.list}${re$2.coord})`, 'g')).map(part => {
|
189 | let coord, front;
|
190 | coord = part.match(new RegExp(`${re$1.coord}`));
|
191 |
|
192 | if (coord) {
|
193 | coord = swu2coord(coord[0]);
|
194 | front = part.slice(0, -4);
|
195 | } else {
|
196 | coord = undefined;
|
197 | front = part;
|
198 | }
|
199 |
|
200 | if (front.includes('o')) {
|
201 | return {
|
202 | or: front.split('o').map(part => {
|
203 | if (!part.includes('R')) {
|
204 | return part;
|
205 | } else {
|
206 | return [part.slice(1, 3), part.slice(3, 5)];
|
207 | }
|
208 | }),
|
209 | coord,
|
210 | coord
|
211 | };
|
212 | } else if (!front.includes('R')) {
|
213 | return {
|
214 | symbol: front,
|
215 | coord: coord
|
216 | };
|
217 | } else {
|
218 | return {
|
219 | range: [front.slice(1, 3), front.slice(3, 5)],
|
220 | coord: coord
|
221 | };
|
222 | }
|
223 | });
|
224 | };
|
225 | /**
|
226 | * Function to parse SWU query string to object
|
227 | * @function swuquery.parse
|
228 | * @param {string} swuQueryString - an SWU query string
|
229 | * @returns {object} elements of an SWU query string
|
230 | * @example
|
231 | * swuquery.parse('QARTR𝤆𝤆V5-')
|
232 | *
|
233 | * return {
|
234 | * query: true,
|
235 | * prefix: {
|
236 | * required: true,
|
237 | * parts: [
|
238 | * '',
|
239 | * ['', ''],
|
240 | * ''
|
241 | * ]
|
242 | * },
|
243 | * signbox: [
|
244 | * { symbol: '' },
|
245 | * {
|
246 | * range: ['', ''],
|
247 | * coord: [500, 500]
|
248 | * }
|
249 | * ],
|
250 | * variance: 5,
|
251 | * style: true
|
252 | * }
|
253 | */
|
254 |
|
255 |
|
256 | const parse$1 = swuQueryString => {
|
257 | const query = typeof swuQueryString === 'string' ? swuQueryString.match(new RegExp(`^${re$2.full}`)) : undefined;
|
258 | return {
|
259 | 'query': query ? true : undefined,
|
260 | 'prefix': query && query[1] ? parsePrefix(query[1]) : undefined,
|
261 | 'signbox': query && query[2] ? parseSignbox(query[2]) : undefined,
|
262 | 'variance': query && query[3] ? parseInt(query[3].slice(1)) : undefined,
|
263 | 'style': query && query[4] ? true : undefined
|
264 | };
|
265 | };
|
266 |
|
267 | /**
|
268 | * Function to compose SWU query string from object
|
269 | * @function swuquery.compose
|
270 | * @param {object} swuQueryObject - an object of style options
|
271 | * @param {boolean} swuQueryObject.query - required true for SWU query object
|
272 | * @param {object} swuQueryObject.prefix - an object for prefix elements
|
273 | * @param {boolean} swuQueryObject.prefix.required - true if sorting prefix is required
|
274 | * @param {(string|string[]|(string|string[])[])[]} swuQueryObject.prefix.parts - array of symbol strings, range arrays, and OR arrays of strings and range arrays
|
275 | * @param {({symbol:string,coord:number[]}|{range:string[],coord:number[]}|{or:(string|string[])[],coord:number[]})[]} swuQueryObject.signbox - array of objects for symbols, ranges, and list of symbols or ranges, with optional coordinates
|
276 | * @param {number} swuQueryObject.variance - amount that x or y coordinates can vary and find a match, defaults to 20
|
277 | * @param {boolean} swuQueryObject.style - boolean value for including style string in matches
|
278 | * @returns {string} SWU query string
|
279 | * @example
|
280 | * swuquery.compose({
|
281 | * query: true,
|
282 | * prefix: {
|
283 | * required: true,
|
284 | * parts: [
|
285 | * '',
|
286 | * ['', ''],
|
287 | * ''
|
288 | * ]
|
289 | * },
|
290 | * signbox: [
|
291 | * { symbol: '' },
|
292 | * {
|
293 | * range: ['', ''],
|
294 | * coord: [500, 500]
|
295 | * }
|
296 | * ],
|
297 | * variance: 5,
|
298 | * style: true
|
299 | * })
|
300 | *
|
301 | * return 'QARTR𝤆𝤆V5-'
|
302 | */
|
303 |
|
304 | const compose = swuQueryObject => {
|
305 | if (!swuQueryObject || !swuQueryObject.query) {
|
306 | return undefined;
|
307 | }
|
308 |
|
309 | let query = 'Q';
|
310 |
|
311 | if (swuQueryObject.prefix && swuQueryObject.prefix.required) {
|
312 | if (Array.isArray(swuQueryObject.prefix.parts)) {
|
313 | query += 'A';
|
314 | query += swuQueryObject.prefix.parts.map(part => {
|
315 | if (typeof part === 'string') {
|
316 | return part;
|
317 | } else {
|
318 | if (Array.isArray(part) && part.length == 2) {
|
319 | return `R${part[0]}${part[1]}`;
|
320 | } else if (Array.isArray(part) && part.length > 2 && part[0] == 'or') {
|
321 | part.shift();
|
322 | return part.map(part => {
|
323 | if (typeof part === 'string') {
|
324 | return part;
|
325 | } else {
|
326 | if (Array.isArray(part) && part.length == 2) {
|
327 | return `R${part[0]}${part[1]}`;
|
328 | }
|
329 | }
|
330 | }).join('o');
|
331 | }
|
332 | }
|
333 | }).join('');
|
334 | }
|
335 |
|
336 | query += 'T';
|
337 | }
|
338 |
|
339 | if (Array.isArray(swuQueryObject.signbox)) {
|
340 | query += swuQueryObject.signbox.map(part => {
|
341 | let out;
|
342 |
|
343 | if (part.or) {
|
344 | out = part.or.map(item => {
|
345 | if (typeof item === 'string') {
|
346 | return item;
|
347 | } else {
|
348 | if (Array.isArray(item) && item.length == 2) {
|
349 | return `R${item[0]}${item[1]}`;
|
350 | }
|
351 | }
|
352 | }).join('o');
|
353 | } else if (part.symbol) {
|
354 | out = part.symbol;
|
355 | } else {
|
356 | if (part.range && Array.isArray(part.range) && part.range.length == 2) {
|
357 | out = `R${part.range[0]}${part.range[1]}`;
|
358 | }
|
359 | }
|
360 |
|
361 | return out + (Array.isArray(part.coord) && part.coord.length == 2 ? coord2swu(part.coord) : '');
|
362 | }).join('');
|
363 | }
|
364 |
|
365 | query += swuQueryObject.style ? '-' : '';
|
366 | query = query.match(new RegExp(`^${re$2.full}`))[0];
|
367 | return query;
|
368 | };
|
369 |
|
370 | /**
|
371 | * Object of regular expressions for style strings
|
372 | *
|
373 | * @alias style.re
|
374 | * @type {object}
|
375 | * @property {string} colorize - regular expression for colorize section
|
376 | * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
|
377 | * @property {string} colorname - regular expression for css color name
|
378 | * @property {string} padding - regular expression for padding section
|
379 | * @property {string} zoom - regular expression for zoom section
|
380 | * @property {string} classbase - regular expression for class name definition
|
381 | * @property {string} id - regular expression for id definition
|
382 | * @property {string} colorbase - regular expression for color hex or color name
|
383 | * @property {string} color - regular expression for single color entry
|
384 | * @property {string} colors - regular expression for double color entry
|
385 | * @property {string} background - regular expression for background section
|
386 | * @property {string} detail - regular expression for color details for line and optional fill
|
387 | * @property {string} detailsym - regular expression for color details for individual symbols
|
388 | * @property {string} classes - regular expression for one or more class names
|
389 | * @property {string} full - full regular expression for style string
|
390 | */
|
391 | let re = {
|
392 | 'colorize': 'C',
|
393 | 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
|
394 | 'colorname': '[a-zA-Z]+',
|
395 | 'padding': 'P[0-9]{2}',
|
396 | 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
|
397 | 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
|
398 | 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
|
399 | };
|
400 | re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
|
401 | re.color = `_${re.colorbase}_`;
|
402 | re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
|
403 | re.background = `G${re.color}`;
|
404 | re.detail = `D${re.colors}`;
|
405 | re.detailsym = `D[0-9]{2}${re.colors}`;
|
406 | re.classes = `${re.classbase}(?: ${re.classbase})*`;
|
407 | re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
|
408 |
|
409 | const parse = {
|
410 | /**
|
411 | * Function to parse an swu symbol with optional coordinate and style string
|
412 | * @function swu.parse.symbol
|
413 | * @param {string} swuSym - an swu symbol
|
414 | * @returns {object} elements of swu symbol
|
415 | * @example
|
416 | * swu.parse.symbol('𝤆𝤆-C')
|
417 | *
|
418 | * return {
|
419 | * 'symbol': '',
|
420 | * 'coord': [500, 500],
|
421 | * 'style': '-C'
|
422 | * }
|
423 | */
|
424 | symbol: swuSym => {
|
425 | const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.full})?`;
|
426 | const symbol = typeof swuSym === 'string' ? swuSym.match(new RegExp(regex)) : undefined;
|
427 | return {
|
428 | 'symbol': symbol ? symbol[1] : undefined,
|
429 | 'coord': symbol && symbol[2] ? swu2coord(symbol[2]) : undefined,
|
430 | 'style': symbol ? symbol[3] : undefined
|
431 | };
|
432 | },
|
433 |
|
434 | /**
|
435 | * Function to parse an swu sign with style string
|
436 | * @function swu.parse.sign
|
437 | * @param {string} swuSign - an swu sign
|
438 | * @returns {object} elements of swu sign
|
439 | * @example
|
440 | * swu.parse.sign('𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭-C')
|
441 | *
|
442 | * return {
|
443 | * sequence: ['','','',''],
|
444 | * box: '𝠃',
|
445 | * max: [525, 535],
|
446 | * spatials: [
|
447 | * {
|
448 | * symbol: '',
|
449 | * coord: [483, 510]
|
450 | * },
|
451 | * {
|
452 | * symbol: '',
|
453 | * coord: [501, 466]
|
454 | * },
|
455 | * {
|
456 | * symbol: '',
|
457 | * coord: [510, 500]
|
458 | * },
|
459 | * {
|
460 | * symbol: '',
|
461 | * coord: [476, 475]
|
462 | * }
|
463 | * ],
|
464 | * style: '-C'
|
465 | * }
|
466 | */
|
467 | sign: swuSign => {
|
468 | const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re.full})?`;
|
469 | const sign = typeof swuSign === 'string' ? swuSign.match(new RegExp(regex)) : undefined;
|
470 |
|
471 | if (sign) {
|
472 | return {
|
473 | 'sequence': sign[1] ? sign[1].slice(2).match(/.{2}/g) : undefined,
|
474 | 'box': sign[2].slice(0, 2),
|
475 | 'max': swu2coord(sign[2].slice(2, 6)),
|
476 | 'spatials': sign[2].length < 7 ? undefined : sign[2].slice(6).match(/(.{6})/g).map(m => {
|
477 | return {
|
478 | symbol: m.slice(0, 2),
|
479 | coord: swu2coord(m.slice(2))
|
480 | };
|
481 | }),
|
482 | 'style': sign[3]
|
483 | };
|
484 | } else {
|
485 | return {};
|
486 | }
|
487 | },
|
488 |
|
489 | /**
|
490 | * Function to parse an swu text
|
491 | * @function swu.parse.text
|
492 | * @param {string} swuText - an swu text
|
493 | * @returns {array} swu signs and punctuations
|
494 | * @example
|
495 | * swu.parse.text('𝠀𝠃𝤘𝤣𝣳𝣩𝤉𝣻 𝠀𝠃𝤘𝤧𝣻𝤕𝣴𝣼𝤎𝤂𝤆𝣦 𝣢𝤂')
|
496 | *
|
497 | * return [
|
498 | * '𝠀𝠃𝤘𝤣𝣳𝣩𝤉𝣻',
|
499 | * '𝠀𝠃𝤘𝤧𝣻𝤕𝣴𝣼𝤎𝤂𝤆𝣦',
|
500 | * '𝣢𝤂'
|
501 | * ]
|
502 | */
|
503 | text: swuText => {
|
504 | if (typeof swuText !== 'string') return [];
|
505 | const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
|
506 | const matches = swuText.match(new RegExp(regex, 'g'));
|
507 | return matches ? [...matches] : [];
|
508 | }
|
509 | };
|
510 | /**
|
511 | * Function to decode UTF-16 escape format to SWU characters.
|
512 | * @function swu.decode
|
513 | * @param {string} encoded - UTF-16 escape format
|
514 | * @returns {string} SWU characters
|
515 | * @example
|
516 | * swu.decode('\\uD8C0\\uDC01\\uD836\\uDD06\\uD836\\uDD06')
|
517 | *
|
518 | * return '𝤆𝤆'
|
519 | */
|
520 |
|
521 |
|
522 | const decode = encoded => encoded.replace(/\\u([0-9A-F]{4})/g, function (match, chr) {
|
523 | return String.fromCharCode(parseInt(chr, 16));
|
524 | });
|
525 | /**
|
526 | * Function to decompose an SWU character into UTF-16 surrogate pairs.
|
527 | * @function swu.pair
|
528 | * @param {string} swuChar - an SWU character
|
529 | * @returns {string[]} an array of UTF-16 surrogate pairs
|
530 | * @example
|
531 | * swu.pair('')
|
532 | *
|
533 | * return ['D8C0', 'DC01']
|
534 | */
|
535 |
|
536 |
|
537 | const pair = swuChar => [swuChar.charCodeAt(0).toString(16).toUpperCase(), swuChar.charCodeAt(1).toString(16).toUpperCase()];
|
538 |
|
539 | /**
|
540 | * Function to convert an SWU sign to a query string
|
541 | *
|
542 | * For the flags parameter, use one or more of the following.
|
543 | * - A: exact symbol in temporal prefix
|
544 | * - a: general symbol in temporal prefix
|
545 | * - S: exact symbol in spatial signbox
|
546 | * - s: general symbol in spatial signbox
|
547 | * - L: spatial signbox symbol at location
|
548 | * @function swuquery.swu2query
|
549 | * @param {string} swuSign - SWU sign
|
550 | * @param {string} flags - flags for query string creation
|
551 | * @returns {string} SWU query string
|
552 | * @example
|
553 | * swuquery.swu2query('𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭', 'ASL')
|
554 | *
|
555 | * return 'QAT𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭'
|
556 | */
|
557 |
|
558 | const swu2query = (swuSign, flags) => {
|
559 | let query = '';
|
560 | const parsed = parse.sign(swuSign);
|
561 |
|
562 | if (parsed.box) {
|
563 | const A_flag = flags.indexOf('A') > -1;
|
564 | const a_flag = flags.indexOf('a') > -1;
|
565 | const S_flag = flags.indexOf('S') > -1;
|
566 | const s_flag = flags.indexOf('s') > -1;
|
567 | const L_flag = flags.indexOf('L') > -1;
|
568 |
|
569 | if (A_flag || a_flag || S_flag || s_flag) {
|
570 | if ((A_flag || a_flag) && parsed.sequence) {
|
571 | query += 'A';
|
572 | query += parsed.sequence.map(sym => sym + (a_flag ? 'fr' : '')).join('');
|
573 | query += 'T';
|
574 | }
|
575 |
|
576 | if ((S_flag || s_flag) && parsed.spatials) {
|
577 | query += parsed.spatials.map(spatial => spatial.symbol + (s_flag ? 'fr' : '') + (L_flag ? coord2swu(spatial.coord) : '')).join('');
|
578 | }
|
579 | }
|
580 |
|
581 | return query ? "Q" + query : undefined;
|
582 | } else {
|
583 | return undefined;
|
584 | }
|
585 | };
|
586 |
|
587 | /**
|
588 | * Function to transform a range of SWU characters to a regular expression
|
589 | * @function swuquery.range
|
590 | * @param {string} min - an SWU character
|
591 | * @param {string} max - an SWU character
|
592 | * @returns {string} a regular expression that matches a range of SWU characters
|
593 | * @example
|
594 | * swuquery.range('', '')
|
595 | *
|
596 | * return '\uD8C0[\uDC01-\uDDE1]'
|
597 | * @example
|
598 | * swuquery.range('𝣔', '𝤸')
|
599 | *
|
600 | * return '\uD836[\uDCD4-\uDD38]'
|
601 | */
|
602 |
|
603 | const range = (min, max) => {
|
604 | if (min > max) return '';
|
605 | let pattern = '';
|
606 | let cnt;
|
607 | let re = [];
|
608 | min = pair(min);
|
609 | max = pair(max);
|
610 | if (min.length != 2 && max.length != 2) return ''; // HEAD // min[0] with range of min[1] to (DFFF or max[1])
|
611 |
|
612 | if (min[0] == max[0]) {
|
613 | if (min[1] == max[1]) {
|
614 | pattern = '\\u' + min[0] + '\\u' + min[1];
|
615 | re.push(pattern);
|
616 | } else {
|
617 | pattern = '\\u' + min[0] + '[\\u' + min[1] + '-\\u' + max[1] + ']';
|
618 | re.push(pattern);
|
619 | }
|
620 | } else {
|
621 | if (min[1] == "DFFF") {
|
622 | pattern = '\\u' + min[0] + '\\uDFFF';
|
623 | } else {
|
624 | pattern = '\\u' + min[0] + '[\\u' + min[1] + '-\\uDFFF]';
|
625 | }
|
626 |
|
627 | re.push(pattern); // BODY // range of (min[0] +1) to (max[0] -1) with all DC00-DFFF
|
628 |
|
629 | let diff = parseInt(max[0], 16) - parseInt(min[0], 16);
|
630 |
|
631 | if (diff == 2) {
|
632 | pattern = '\\u' + (parseInt(min[0], 16) + 1).toString(16).toUpperCase();
|
633 | pattern += '[\\uDC00-\\uDFFF]';
|
634 | re.push(pattern);
|
635 | }
|
636 |
|
637 | if (diff > 2) {
|
638 | pattern = '[';
|
639 | pattern += '\\u' + (parseInt(min[0], 16) + 1).toString(16).toUpperCase();
|
640 | pattern += '-\\u' + (parseInt(max[0], 16) - 1).toString(16).toUpperCase();
|
641 | pattern += '][\\uDC00-\\uDFFF]';
|
642 | re.push(pattern);
|
643 | } // TAIL // max[0] with range of DC00 to max[1]
|
644 |
|
645 |
|
646 | if (max[1] == "DC00") {
|
647 | pattern = '\\u' + max[0] + '\\uDC00';
|
648 | } else {
|
649 | pattern = '\\u' + max[0] + '[\\uDC00-\\u' + max[1] + ']';
|
650 | }
|
651 |
|
652 | re.push(pattern);
|
653 | }
|
654 |
|
655 | cnt = re.length;
|
656 |
|
657 | if (cnt == 1) {
|
658 | pattern = re[0];
|
659 | } else {
|
660 | pattern = re.join(')|(');
|
661 | pattern = '((' + pattern + '))';
|
662 | }
|
663 |
|
664 | return decode(pattern);
|
665 | };
|
666 |
|
667 | /**
|
668 | * Function to transform an SWU symbol with fill and rotation flags to a regular expression
|
669 | * @function swuquery.symbolRanges
|
670 | * @param {string} symbolFR - an SWU character with optional flags of 'f' for any fill and 'r' for any rotation
|
671 | * @returns {string} a regular expression that matches one or more ranges of SWU symbols
|
672 | * @example <caption>Match an exact symbol</caption>
|
673 | * swuquery.symbolRanges('')
|
674 | *
|
675 | * return '\uD8C0\uDC01');
|
676 | * @example <caption>Match a symbol with any fill</caption>
|
677 | * swuquery.symbolRanges('f')
|
678 | *
|
679 | * return '(\uD8C0\uDC01|\uD8C0\uDC11|\uD8C0\uDC21|\uD8C0\uDC31|\uD8C0\uDC41|\uD8C0\uDC51)'
|
680 | * @example <caption>Match a symbol with any rotation</caption>
|
681 | * swuquery.symbolRanges('r')
|
682 | *
|
683 | * return '\uD8C0[\uDC01-\uDC10]'
|
684 | * @example <caption>Match a symbol with any fill or rotation</caption>
|
685 | * swuquery.symbolRanges('fr')
|
686 | *
|
687 | * return '\uD8C0[\uDC01-\uDC60]'
|
688 | */
|
689 |
|
690 | const symbolRanges = symbolFR => {
|
691 | let match = symbolFR.match(new RegExp(re$2.symbol));
|
692 |
|
693 | if (match) {
|
694 | let sym = match[0].slice(0, 2);
|
695 | let key = swu2key(sym);
|
696 | let base = key.slice(0, 4);
|
697 | let start, end;
|
698 |
|
699 | if (match[0].slice(-2) == 'fr') {
|
700 | start = key2swu(base + "00");
|
701 | end = key2swu(base + "5f");
|
702 | return range(start, end);
|
703 | } else if (match[0].slice(-1) == 'r') {
|
704 | start = key2swu(key.slice(0, 5) + '0');
|
705 | end = key2swu(key.slice(0, 5) + 'f');
|
706 | return range(start, end);
|
707 | } else if (match[0].slice(-1) == 'f') {
|
708 | let list = [0, 1, 2, 3, 4, 5].map(function (f) {
|
709 | return key2swu(base + f + key.slice(-1));
|
710 | });
|
711 | return "(" + list.join("|") + ")";
|
712 | } else {
|
713 | return sym;
|
714 | }
|
715 | } else {
|
716 | return '';
|
717 | }
|
718 | };
|
719 |
|
720 | const regexRange = symRange => {
|
721 | from = swu2key(symRange.slice(1, 3));
|
722 | to = swu2key(symRange.slice(-2));
|
723 | from = key2swu(from.slice(0, 4) + '00');
|
724 | to = key2swu(to.slice(0, 4) + '5f');
|
725 | return range(from, to);
|
726 | }; //needs rewritten, but it works
|
727 |
|
728 | /**
|
729 | * Function to transform an SWU query string to one or more regular expressions
|
730 | * @function swuquery.regex
|
731 | * @param {string} query - an SWU query string
|
732 | * @returns {string[]} an array of one or more regular expressions
|
733 | * @example
|
734 | * swuquery.regex('QAT')
|
735 | *
|
736 | * return [
|
737 | * '(\uD836\uDC00\uD8C0\uDC12((?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80])))*)\uD836[\uDC01-\uDC04](?:\uD836[\uDC0C-\uDDFF]){2}((?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))(?:\uD836[\uDC0C-\uDDFF]){2})*'
|
738 | * ]
|
739 | */
|
740 |
|
741 |
|
742 | const regex = query => {
|
743 | query = query.match(new RegExp(`^${re$2.full}`))[0];
|
744 |
|
745 | if (!query) {
|
746 | return '';
|
747 | }
|
748 |
|
749 | let matches;
|
750 | let matchesOr;
|
751 | let matched;
|
752 | let orList;
|
753 | let i;
|
754 | let j;
|
755 | let coord;
|
756 | let segment;
|
757 | let x;
|
758 | let y;
|
759 | let fuzz = 20;
|
760 | let re_sym = re$1.symbol;
|
761 | let re_coord = re$1.coord;
|
762 | let re_signbox = re$1.box;
|
763 | let re_seq = re$1.sort;
|
764 | let re_word = re_signbox + re_coord + '(' + re_sym + re_coord + ')*';
|
765 | let re_sortable = '(' + re_seq + '(' + re_sym + ')+)';
|
766 | let q_var = '(V[0-9]+)';
|
767 | let q_style = '(' + re.full + ')?';
|
768 | let q_sortable;
|
769 |
|
770 | if (query == 'Q') {
|
771 | return [re$1.sign];
|
772 | }
|
773 |
|
774 | if (query == 'Q-') {
|
775 | return [re$1.sign + "(" + re.full + ")?"];
|
776 | }
|
777 |
|
778 | if (query == 'QT') {
|
779 | return [re$1.sortable];
|
780 | }
|
781 |
|
782 | if (query == 'QT-') {
|
783 | return [re$1.sortable + "(" + re.full + ")?"];
|
784 | }
|
785 |
|
786 | let segments = [];
|
787 | let sortable = query.indexOf('T') + 1;
|
788 |
|
789 | if (sortable) {
|
790 | q_sortable = '(' + re$1.sort;
|
791 | let qat = query.slice(0, sortable);
|
792 | query = query.replace(qat, '');
|
793 |
|
794 | if (qat == 'QT') {
|
795 | q_sortable += '(' + re_sym + ')+)';
|
796 | } else {
|
797 | matches = qat.match(new RegExp('(' + re$2.list + ')', 'g'));
|
798 |
|
799 | if (matches) {
|
800 | for (i = 0; i < matches.length; i += 1) {
|
801 | orList = [];
|
802 | matchesOr = matches[i].match(new RegExp('(' + re$2.symbol + '|' + re$2.range + ')', 'g'));
|
803 |
|
804 | if (matchesOr) {
|
805 | for (j = 0; j < matchesOr.length; j += 1) {
|
806 | matched = matchesOr[j].match(new RegExp(re$2.symbol));
|
807 |
|
808 | if (matched) {
|
809 | orList.push(symbolRanges(matched[0]));
|
810 | } else {
|
811 | orList.push(regexRange(matchesOr[j]));
|
812 | }
|
813 | }
|
814 |
|
815 | if (orList.length == 1) {
|
816 | q_sortable += orList[0];
|
817 | } else {
|
818 | q_sortable += '(' + orList.join('|') + ')';
|
819 | }
|
820 | }
|
821 | }
|
822 |
|
823 | q_sortable += '(' + re$1.symbol + ')*)';
|
824 | }
|
825 | }
|
826 | } //get the variance
|
827 |
|
828 |
|
829 | matches = query.match(new RegExp(q_var, 'g'));
|
830 |
|
831 | if (matches) {
|
832 | fuzz = matches.toString().slice(1) * 1;
|
833 | } //this gets all symbols and ranges with or without location
|
834 |
|
835 |
|
836 | matches = query.match(new RegExp(re$2.list + re$2.coord, 'g'));
|
837 |
|
838 | if (matches) {
|
839 | for (i = 0; i < matches.length; i += 1) {
|
840 | orList = [];
|
841 | matchesOr = matches[i].match(new RegExp('(' + re$2.symbol + '|' + re$2.range + ')', 'g'));
|
842 |
|
843 | if (matchesOr) {
|
844 | for (j = 0; j < matchesOr.length; j += 1) {
|
845 | matched = matchesOr[j].match(new RegExp(re$2.symbol));
|
846 |
|
847 | if (matched) {
|
848 | orList.push(symbolRanges(matched[0]));
|
849 | } else {
|
850 | orList.push(regexRange(matchesOr[j]));
|
851 | }
|
852 | }
|
853 |
|
854 | if (orList.length == 1) {
|
855 | segment = orList[0];
|
856 | } else {
|
857 | segment = '(' + orList.join('|') + ')';
|
858 | }
|
859 | }
|
860 |
|
861 | coord = matches[i].match(new RegExp(`${re$1.coord}`));
|
862 |
|
863 | if (coord) {
|
864 | coord = swu2coord(coord[0]);
|
865 | x = coord[0];
|
866 | y = coord[1];
|
867 | segment += range(num2swu(x - fuzz), num2swu(x + fuzz));
|
868 | segment += range(num2swu(y - fuzz), num2swu(y + fuzz));
|
869 | } else {
|
870 | segment += re$1.coord;
|
871 | } // add to general swu word
|
872 |
|
873 |
|
874 | segment = re_word + segment + '(' + re_sym + re_coord + ')*';
|
875 |
|
876 | if (sortable) {
|
877 | segment = q_sortable + segment;
|
878 | } else {
|
879 | segment = re_sortable + "?" + segment;
|
880 | }
|
881 |
|
882 | if (query.indexOf('-') > 0) {
|
883 | segment += q_style;
|
884 | }
|
885 |
|
886 | segments.push(segment);
|
887 | }
|
888 | }
|
889 |
|
890 | if (!segments.length) {
|
891 | if (query.indexOf('-') > 0) {
|
892 | segment += q_style;
|
893 | }
|
894 |
|
895 | segments.push(q_sortable + re_word);
|
896 | }
|
897 |
|
898 | return segments;
|
899 | };
|
900 |
|
901 | /**
|
902 | * Function that uses a query string to match signs from a string of text.
|
903 | * @function swuquery.results
|
904 | * @param {string} query - an SWU query string
|
905 | * @param {string} text - a string of text containing multiple signs
|
906 | * @returns {string[]} an array of SWU signs
|
907 | * @example
|
908 | * swuquery.results('QAT','𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭 𝠀𝠃𝤛𝤬𝤀𝣺𝤄𝣻𝤄𝤗𝤃𝣟𝣱𝣸 𝠀𝠃𝤙𝤞𝣷𝤀𝣼𝤀𝣳𝣮')
|
909 | *
|
910 | * return [
|
911 | * '𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭'
|
912 | * ]
|
913 | */
|
914 |
|
915 | const results = (query, text) => {
|
916 | if (!text) {
|
917 | return [];
|
918 | }
|
919 |
|
920 | let pattern;
|
921 | let matches;
|
922 | let parts;
|
923 | let words;
|
924 | let res = regex(query);
|
925 |
|
926 | if (!res) {
|
927 | return [];
|
928 | }
|
929 |
|
930 | let i;
|
931 |
|
932 | for (i = 0; i < res.length; i += 1) {
|
933 | pattern = res[i];
|
934 | matches = text.match(new RegExp(pattern, 'g'));
|
935 |
|
936 | if (matches) {
|
937 | text = matches.join(' ');
|
938 | } else {
|
939 | text = '';
|
940 | }
|
941 | }
|
942 |
|
943 | if (text) {
|
944 | parts = text.split(' ');
|
945 | words = parts.filter(function (element) {
|
946 | return element in parts ? false : parts[element] = true;
|
947 | }, {});
|
948 | } else {
|
949 | words = [];
|
950 | }
|
951 |
|
952 | return words;
|
953 | }; //needs rewritten, but it works
|
954 |
|
955 | /**
|
956 | * Function that uses an SWU query string to match signs from multiple lines of text.
|
957 | * @function swuquery.lines
|
958 | * @param {string} query - an SWU query string
|
959 | * @param {string} text - multiple lines of text, each starting with an SWU sign
|
960 | * @returns {string[]} an array of lines of text, each starting with an SWU sign
|
961 | * @example
|
962 | * swuquery.lines('QAT',`𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭 line one
|
963 | * 𝠀𝠃𝤛𝤬𝤀𝣺𝤄𝣻𝤄𝤗𝤃𝣟𝣱𝣸 line two
|
964 | * 𝠀𝠃𝤙𝤞𝣷𝤀𝣼𝤀𝣳𝣮 line three`)
|
965 | *
|
966 | * return [
|
967 | * '𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭 line one'
|
968 | * ]
|
969 | */
|
970 |
|
971 |
|
972 | const lines = (query, text) => {
|
973 | if (!text) {
|
974 | return [];
|
975 | }
|
976 |
|
977 | let pattern;
|
978 | let matches;
|
979 | let parts;
|
980 | let words;
|
981 | let res = regex(query);
|
982 |
|
983 | if (!res) {
|
984 | return [];
|
985 | }
|
986 |
|
987 | let i;
|
988 |
|
989 | for (i = 0; i < res.length; i += 1) {
|
990 | pattern = res[i];
|
991 | pattern = '^' + pattern + '.*';
|
992 | matches = text.match(new RegExp(pattern, 'mg'));
|
993 |
|
994 | if (matches) {
|
995 | text = matches.join("\n");
|
996 | } else {
|
997 | text = '';
|
998 | }
|
999 | }
|
1000 |
|
1001 | if (text) {
|
1002 | parts = text.split("\n");
|
1003 | words = parts.filter(function (element) {
|
1004 | return element in parts ? false : parts[element] = true;
|
1005 | }, {});
|
1006 | } else {
|
1007 | words = [];
|
1008 | }
|
1009 |
|
1010 | return words;
|
1011 | };
|
1012 |
|
1013 | exports.compose = compose;
|
1014 | exports.lines = lines;
|
1015 | exports.parse = parse$1;
|
1016 | exports.range = range;
|
1017 | exports.re = re$2;
|
1018 | exports.regex = regex;
|
1019 | exports.results = results;
|
1020 | exports.swu2query = swu2query;
|
1021 | exports.symbolRanges = symbolRanges;
|
1022 |
|
1023 | Object.defineProperty(exports, '__esModule', { value: true });
|
1024 |
|
1025 | }));
|
1026 |
|
1027 | /* support ongoing development on https://patreon.com/signwriting */
|