UNPKG

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