UNPKG

30.9 kBJavaScriptView Raw
1/**
2* Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core)
3* Author: Steve Slevinski (https://SteveSlevinski.me)
4* fswquery.mjs is released under the MIT License.
5*/
6
7/**
8 * Object of regular expressions for FSW query strings
9 *
10 * { base, coord, var, symbol, range, prefix, signbox, full }
11 * @alias fswquery.re
12 * @type {object}
13 */
14let re = {
15 'base': '[123][0-9a-f]{2}',
16 'coord': '(?:[0-9]{3}x[0-9]{3})?',
17 'var': 'V[0-9]+'
18};
19re.symbol = `S${re.base}[0-5u][0-9a-fu]`;
20re.range = `R${re.base}t${re.base}`;
21re.prefix = `(?:A(?:${re.symbol}|${re.range})+)?T`;
22re.signbox = `(?:${re.symbol}${re.coord}|${re.range}${re.coord})*`;
23re.full = `Q(${re.prefix})?(${re.signbox})?(${re.var})?(-?)`;
24
25/**
26 * Object of regular expressions for FSW strings
27 *
28 * { symbol, coord, sort, box, prefix, spatial, signbox, sign, sortable }
29 * @alias fsw.re
30 * @type {object}
31 */
32let re$1 = {
33 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
34 'coord': '[0-9]{3}x[0-9]{3}',
35 'sort': 'A',
36 'box': '[BLMR]'
37};
38re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
39re$1.spatial = `${re$1.symbol}${re$1.coord}`;
40re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
41re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
42re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
43
44/** 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.
45 * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2)
46 * @module convert
47 */
48/**
49 * Function to convert an FSW coordinate string to an array of x,y integers
50 * @function convert.fsw2coord
51 * @param {string} fswCoord - An FSW coordinate string
52 * @returns {number[]} Array of x,y integers
53 * @example
54 * convert.fsw2coord('500x500')
55 *
56 * return [500, 500]
57 */
58
59
60const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
61
62const parsePrefix = text => {
63 return {
64 required: true,
65 parts: text == 'T' ? undefined : text.match(new RegExp(`(${re.symbol}|${re.range})`, 'g')).map(part => part[0] == 'S' ? part : part.slice(1).split('t'))
66 };
67};
68
69const parseSignbox = text => {
70 return text.match(new RegExp(`(${re.symbol}${re.coord}|${re.range}${re.coord})`, 'g')).map(part => {
71 let coord, front;
72
73 if (part.includes('x')) {
74 coord = fsw2coord(part.slice(-7));
75 front = part.slice(0, -7);
76 } else {
77 front = part;
78 }
79
80 if (front.includes('S')) {
81 return {
82 symbol: front,
83 coord: coord
84 };
85 } else {
86 return {
87 range: front.slice(1).split('t'),
88 coord: coord
89 };
90 }
91 });
92};
93/**
94 * Function to parse FSW query string to object
95 * @function fswquery.parse
96 * @param {string} fswQueryString - an FSW query string
97 * @returns {object} elements of an FSW query string
98 * @example
99 * fswquery.parse('QAS10000R100t204S20500TS20000R100t105500x500V5-')
100 *
101 * return {
102 * query: true,
103 * prefix: {
104 * required: true,
105 * parts: [
106 * 'S10000',
107 * ['100', '204'],
108 * 'S20500'
109 * ]
110 * },
111 * signbox: [
112 * { symbol: 'S20000' },
113 * {
114 * range: ['100', '105'],
115 * coord: [500, 500]
116 * }
117 * ],
118 * variance: 5,
119 * style: true
120 * }
121 */
122
123
124const parse = fswQueryString => {
125 const query = typeof fswQueryString === 'string' ? fswQueryString.match(new RegExp(`^${re.full}`)) : undefined;
126 return {
127 'query': query ? true : undefined,
128 'prefix': query && query[1] ? parsePrefix(query[1]) : undefined,
129 'signbox': query && query[2] ? parseSignbox(query[2]) : undefined,
130 'variance': query && query[3] ? parseInt(query[3].slice(1)) : undefined,
131 'style': query && query[4] ? true : undefined
132 };
133};
134
135/**
136 * Function to compose FSW query string from object
137 * @function fswquery.compose
138 * @param {object} fswQueryObject - an object of style options
139 * @param {boolean} fswQueryObject.query - required true for FSW query object
140 * @param {object} fswQueryObject.prefix - an object for prefix elements
141 * @param {boolean} fswQueryObject.prefix.required - true if sorting prefix is required
142 * @param {(string|string[])[]} fswQueryObject.prefix.parts - array of symbol strings and range arrays
143 * @param {({symbol:string,coord:number[]}|{range:string[],coord:number[]})[]} fswQueryObject.signbox - array of objects for symbols and ranges with optional coordinates
144 * @param {number} fswQueryObject.variance - amount that x or y coordinates can vary and find a match, defaults to 20
145 * @param {boolean} fswQueryObject.style - boolean value for including style string in matches
146 * @returns {string} FSW query string
147 * @example
148 * fswquery.compose({
149 * query: true,
150 * prefix: {
151 * required: true,
152 * parts: [
153 * 'S10000',
154 * ['100', '204'],
155 * 'S20500'
156 * ]
157 * },
158 * signbox: [
159 * { symbol: 'S20000' },
160 * {
161 * range: ['100', '105'],
162 * coord: [500, 500]
163 * }
164 * ],
165 * variance: 5,
166 * style: true
167 * })
168 *
169 * return 'QAS10000R100t204S20500TS20000R100t105500x500V5-'
170 */
171
172const compose = fswQueryObject => {
173 if (!fswQueryObject || !fswQueryObject.query) {
174 return undefined;
175 }
176
177 let query = 'Q';
178
179 if (fswQueryObject.prefix && fswQueryObject.prefix.required) {
180 if (Array.isArray(fswQueryObject.prefix.parts)) {
181 query += 'A';
182 query += fswQueryObject.prefix.parts.map(part => {
183 if (typeof part === 'string') {
184 return part;
185 } else {
186 if (Array.isArray(part) && part.length == 2) {
187 return `R${part[0]}t${part[1]}`;
188 }
189 }
190 }).join('');
191 }
192
193 query += 'T';
194 }
195
196 if (Array.isArray(fswQueryObject.signbox)) {
197 query += fswQueryObject.signbox.map(part => {
198 let out;
199
200 if (part.symbol) {
201 out = part.symbol;
202 } else {
203 if (part.range && Array.isArray(part.range) && part.range.length == 2) {
204 out = `R${part.range[0]}t${part.range[1]}`;
205 }
206 }
207
208 return out + (Array.isArray(part.coord) && part.coord.length == 2 ? part.coord.join('x') : '');
209 }).join('');
210 }
211
212 query += fswQueryObject.style ? '-' : '';
213 query = query.match(new RegExp(`^${re.full}`))[0];
214 return query;
215};
216
217/**
218 * Object of regular expressions for style strings
219 *
220 * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full }
221 * @alias style.re
222 * @type {object}
223 */
224let re$2 = {
225 'colorize': 'C',
226 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
227 'colorname': '[a-zA-Z]+',
228 'padding': 'P[0-9]{2}',
229 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
230 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?',
231 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
232 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
233};
234re$2.colorbase = `(?:${re$2.colorhex}|${re$2.colorname})`;
235re$2.color = `_${re$2.colorbase}_`;
236re$2.colors = `_${re$2.colorbase}(?:,${re$2.colorbase})?_`;
237re$2.background = `G${re$2.color}`;
238re$2.detail = `D${re$2.colors}`;
239re$2.detailsym = `D[0-9]{2}${re$2.colors}`;
240re$2.classes = `${re$2.classbase}(?: ${re$2.classbase})*`;
241re$2.full = `-(${re$2.colorize})?(${re$2.padding})?(${re$2.background})?(${re$2.detail})?(${re$2.zoom})?(?:-((?:${re$2.detailsym})*)((?:${re$2.zoomsym})*))?(?:-(${re$2.classes})?!(?:(${re$2.id})!)?)?`;
242
243const parse$1 = {
244 /**
245 * Function to parse an fsw symbol with optional coordinate and style string
246 * @function fsw.parse.symbol
247 * @param {string} fswSym - an fsw symbol
248 * @returns {object} elements of fsw symbol
249 * @example
250 * fsw.parse.symbol('S10000500x500-C')
251 *
252 * return {
253 * 'symbol': 'S10000',
254 * 'coord': [500, 500],
255 * 'style': '-C'
256 * }
257 */
258 symbol: fswSym => {
259 const regex = `^(${re$1.symbol})(${re$1.coord})?(${re$2.full})?`;
260 const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
261 return {
262 'symbol': symbol ? symbol[1] : undefined,
263 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
264 'style': symbol ? symbol[3] : undefined
265 };
266 },
267
268 /**
269 * Function to parse an fsw sign with style string
270 * @function fsw.parse.sign
271 * @param {string} fswSign - an fsw sign
272 * @returns {object} elements of fsw sign
273 * @example
274 * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
275 *
276 * return {
277 * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
278 * box: 'M',
279 * max: [525, 535],
280 * spatials: [
281 * {
282 * symbol: 'S2e748',
283 * coord: [483, 510]
284 * },
285 * {
286 * symbol: 'S10011',
287 * coord: [501, 466]
288 * },
289 * {
290 * symbol: 'S2e704',
291 * coord: [510, 500]
292 * },
293 * {
294 * symbol: 'S10019',
295 * coord: [476, 475]
296 * }
297 * ],
298 * style: '-C'
299 * }
300 */
301 sign: fswSign => {
302 const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re$2.full})?`;
303 const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
304
305 if (sign) {
306 return {
307 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
308 'box': sign[2][0],
309 'max': fsw2coord(sign[2].slice(1, 8)),
310 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
311 return {
312 symbol: m.slice(0, 6),
313 coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
314 };
315 }),
316 'style': sign[3]
317 };
318 } else {
319 return {};
320 }
321 }
322};
323
324/**
325 * Function to convert an FSW sign to a query string
326 *
327 * For the flags parameter, use one or more of the following.
328 * - A: exact symbol in temporal prefix
329 * - a: general symbol in temporal prefix
330 * - S: exact symbol in spatial signbox
331 * - s: general symbol in spatial signbox
332 * - L: spatial signbox symbol at location
333 * @function fswquery.fsw2query
334 * @param {string} fswSign - FSW sign
335 * @param {string} flags - flags for query string creation
336 * @returns {string} FSW query string
337 * @example
338 * fswquery.fsw2query('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475', 'ASL')
339 *
340 * return 'QAS10011S10019S2e704S2e748TS2e748483x510S10011501x466S2e704510x500S10019476x475'
341 */
342
343const fsw2query = (fswSign, flags) => {
344 let query = '';
345 const parsed = parse$1.sign(fswSign);
346
347 if (parsed.box) {
348 const A_flag = flags.indexOf('A') > -1;
349 const a_flag = flags.indexOf('a') > -1;
350 const S_flag = flags.indexOf('S') > -1;
351 const s_flag = flags.indexOf('s') > -1;
352 const L_flag = flags.indexOf('L') > -1;
353
354 if (A_flag || a_flag || S_flag || s_flag) {
355 if ((A_flag || a_flag) && parsed.sequence) {
356 query += 'A';
357 query += parsed.sequence.map(sym => sym.slice(0, 4) + (a_flag ? 'uu' : sym.slice(4, 6))).join('');
358 query += 'T';
359 }
360
361 if ((S_flag || s_flag) && parsed.spatials) {
362 query += parsed.spatials.map(spatial => spatial.symbol.slice(0, 4) + (s_flag ? 'uu' : spatial.symbol.slice(4, 6)) + (L_flag ? spatial.coord.join('x') : '')).join('');
363 }
364 }
365
366 return query ? "Q" + query : undefined;
367 } else {
368 return undefined;
369 }
370};
371
372//needs rewritten, but it works
373
374/**
375 * Function to transform a range to a regular expression
376 * @function fswquery.range
377 * @param {(number|string)} min - either a decimal number or hexidecimal string
378 * @param {(number|string)} max - either a decimal number or hexidecimal string
379 * @param {boolean?} hex - if true, the regular expression will match a hexidecimal range
380 * @returns {string} a regular expression that matches a range
381 * @example
382 * fswquery.range(500,749)
383 *
384 * return '(([56][0-9][0-9])|(7[0-4][0-9])|(750))'
385 * @example
386 * fswquery.range('100','10e',true)
387 *
388 * return '10[0-9a-e]'
389 */
390const range = (min, max, hex) => {
391 let pattern;
392 let re;
393 let diff;
394 let tmax;
395 let cnt;
396 let minV;
397 let maxV;
398
399 if (!hex) {
400 hex = '';
401 }
402
403 min = ("000" + min).slice(-3);
404 max = '' + max;
405 pattern = '';
406
407 if (min === max) {
408 return min;
409 } //ending pattern will be series of connected OR ranges
410
411
412 re = []; //first pattern+ 10's don't match and the min 1's are not zero
413 //odd number to 9
414
415 if (!(min[0] == max[0] && min[1] == max[1])) {
416 if (min[2] != '0') {
417 pattern = min[0] + min[1];
418
419 if (hex) {
420 //switch for dex
421 switch (min[2]) {
422 case "f":
423 pattern += 'f';
424 break;
425
426 case "e":
427 pattern += '[ef]';
428 break;
429
430 case "d":
431 case "c":
432 case "b":
433 case "a":
434 pattern += '[' + min[2] + '-f]';
435 break;
436
437 default:
438 switch (min[2]) {
439 case "9":
440 pattern += '[9a-f]';
441 break;
442
443 case "8":
444 pattern += '[89a-f]';
445 break;
446
447 default:
448 pattern += '[' + min[2] + '-9a-f]';
449 break;
450 }
451
452 break;
453 }
454
455 diff = 15 - parseInt(min[2], 16) + 1;
456 min = '' + (parseInt(min, 16) + diff).toString(16);
457 re.push(pattern);
458 } else {
459 //switch for dex
460 switch (min[2]) {
461 case "9":
462 pattern += '9';
463 break;
464
465 case "8":
466 pattern += '[89]';
467 break;
468
469 default:
470 pattern += '[' + min[2] + '-9]';
471 break;
472 }
473
474 diff = 9 - min[2] + 1;
475 min = '' + (min * 1 + diff);
476 re.push(pattern);
477 }
478 }
479 }
480
481 pattern = ''; //if hundreds are different, get odd to 99 or ff
482
483 if (min[0] != max[0]) {
484 if (min[1] != '0') {
485 if (hex) {
486 //scrape to ff
487 pattern = min[0];
488
489 switch (min[1]) {
490 case "f":
491 pattern += 'f';
492 break;
493
494 case "e":
495 pattern += '[ef]';
496 break;
497
498 case "d":
499 case "c":
500 case "b":
501 case "a":
502 pattern += '[' + min[1] + '-f]';
503 break;
504
505 case "9":
506 pattern += '[9a-f]';
507 break;
508
509 case "8":
510 pattern += '[89a-f]';
511 break;
512
513 default:
514 pattern += '[' + min[1] + '-9a-f]';
515 break;
516 }
517
518 pattern += '[0-9a-f]';
519 diff = 15 - parseInt(min[1], 16) + 1;
520 min = '' + (parseInt(min, 16) + diff * 16).toString(16);
521 re.push(pattern);
522 } else {
523 //scrape to 99
524 pattern = min[0];
525 diff = 9 - min[1] + 1;
526
527 switch (min[1]) {
528 case "9":
529 pattern += '9';
530 break;
531
532 case "8":
533 pattern += '[89]';
534 break;
535
536 default:
537 pattern += '[' + min[1] + '-9]';
538 break;
539 }
540
541 pattern += '[0-9]';
542 diff = 9 - min[1] + 1;
543 min = '' + (min * 1 + diff * 10);
544 re.push(pattern);
545 }
546 }
547 }
548
549 pattern = ''; //if hundreds are different, get to same
550
551 if (min[0] != max[0]) {
552 if (hex) {
553 diff = parseInt(max[0], 16) - parseInt(min[0], 16);
554 tmax = (parseInt(min[0], 16) + diff - 1).toString(16);
555
556 switch (diff) {
557 case 1:
558 pattern = min[0];
559 break;
560
561 case 2:
562 pattern = '[' + min[0] + tmax + ']';
563 break;
564
565 default:
566 if (parseInt(min[0], 16) > 9) {
567 minV = 'h';
568 } else {
569 minV = 'd';
570 }
571
572 if (parseInt(tmax, 16) > 9) {
573 maxV = 'h';
574 } else {
575 maxV = 'd';
576 }
577
578 switch (minV + maxV) {
579 case "dd":
580 pattern += '[' + min[0] + '-' + tmax + ']';
581 break;
582
583 case "dh":
584 diff = 9 - min[0]; //firs get up to 9
585
586 switch (diff) {
587 case 0:
588 pattern += '[9';
589 break;
590
591 case 1:
592 pattern += '[89';
593 break;
594
595 default:
596 pattern += '[' + min[0] + '-9';
597 break;
598 }
599
600 switch (tmax[0]) {
601 case 'a':
602 pattern += 'a]';
603 break;
604
605 case 'b':
606 pattern += 'ab]';
607 break;
608
609 default:
610 pattern += 'a-' + tmax + ']';
611 break;
612 }
613
614 break;
615
616 case "hh":
617 pattern += '[' + min[0] + '-' + tmax + ']';
618 break;
619 }
620
621 }
622
623 pattern += '[0-9a-f][0-9a-f]';
624 diff = parseInt(max[0], 16) - parseInt(min[0], 16);
625 min = '' + (parseInt(min, 16) + diff * 256).toString(16);
626 re.push(pattern);
627 } else {
628 diff = max[0] - min[0];
629 tmax = min[0] * 1 + diff - 1;
630
631 switch (diff) {
632 case 1:
633 pattern = min[0];
634 break;
635
636 case 2:
637 pattern = '[' + min[0] + tmax + ']';
638 break;
639
640 default:
641 pattern = '[' + min[0] + '-' + tmax + ']';
642 break;
643 }
644
645 pattern += '[0-9][0-9]';
646 min = '' + (min * 1 + diff * 100);
647 re.push(pattern);
648 }
649 }
650
651 pattern = ''; //if tens are different, get to same
652
653 if (min[1] != max[1]) {
654 if (hex) {
655 diff = parseInt(max[1], 16) - parseInt(min[1], 16);
656 tmax = (parseInt(min[1], 16) + diff - 1).toString(16);
657 pattern = min[0];
658
659 switch (diff) {
660 case 1:
661 pattern += min[1];
662 break;
663
664 case 2:
665 pattern += '[' + min[1] + tmax + ']';
666 break;
667
668 default:
669 if (parseInt(min[1], 16) > 9) {
670 minV = 'h';
671 } else {
672 minV = 'd';
673 }
674
675 if (parseInt(tmax, 16) > 9) {
676 maxV = 'h';
677 } else {
678 maxV = 'd';
679 }
680
681 switch (minV + maxV) {
682 case "dd":
683 pattern += '[' + min[1];
684
685 if (diff > 1) {
686 pattern += '-';
687 }
688
689 pattern += tmax + ']';
690 break;
691
692 case "dh":
693 diff = 9 - min[1]; //firs get up to 9
694
695 switch (diff) {
696 case 0:
697 pattern += '[9';
698 break;
699
700 case 1:
701 pattern += '[89';
702 break;
703
704 default:
705 pattern += '[' + min[1] + '-9';
706 break;
707 }
708
709 switch (max[1]) {
710 case 'a':
711 pattern += ']';
712 break;
713
714 case 'b':
715 pattern += 'a]';
716 break;
717
718 default:
719 pattern += 'a-' + (parseInt(max[1], 16) - 1).toString(16) + ']';
720 break;
721 }
722
723 break;
724
725 case "hh":
726 pattern += '[' + min[1];
727
728 if (diff > 1) {
729 pattern += '-';
730 }
731
732 pattern += (parseInt(max[1], 16) - 1).toString(16) + ']';
733 break;
734 }
735
736 break;
737 }
738
739 pattern += '[0-9a-f]';
740 diff = parseInt(max[1], 16) - parseInt(min[1], 16);
741 min = '' + (parseInt(min, 16) + diff * 16).toString(16);
742 re.push(pattern);
743 } else {
744 diff = max[1] - min[1];
745 tmax = min[1] * 1 + diff - 1;
746 pattern = min[0];
747
748 switch (diff) {
749 case 1:
750 pattern += min[1];
751 break;
752
753 case 2:
754 pattern += '[' + min[1] + tmax + ']';
755 break;
756
757 default:
758 pattern += '[' + min[1] + '-' + tmax + ']';
759 break;
760 }
761
762 pattern += '[0-9]';
763 min = '' + (min * 1 + diff * 10);
764 re.push(pattern);
765 }
766 }
767
768 pattern = ''; //if digits are different, get to same
769
770 if (min[2] != max[2]) {
771 if (hex) {
772 pattern = min[0] + min[1];
773 diff = parseInt(max[2], 16) - parseInt(min[2], 16);
774
775 if (parseInt(min[2], 16) > 9) {
776 minV = 'h';
777 } else {
778 minV = 'd';
779 }
780
781 if (parseInt(max[2], 16) > 9) {
782 maxV = 'h';
783 } else {
784 maxV = 'd';
785 }
786
787 switch (minV + maxV) {
788 case "dd":
789 pattern += '[' + min[2];
790
791 if (diff > 1) {
792 pattern += '-';
793 }
794
795 pattern += max[2] + ']';
796 break;
797
798 case "dh":
799 diff = 9 - min[2]; //firs get up to 9
800
801 switch (diff) {
802 case 0:
803 pattern += '[9';
804 break;
805
806 case 1:
807 pattern += '[89';
808 break;
809
810 default:
811 pattern += '[' + min[2] + '-9';
812 break;
813 }
814
815 switch (max[2]) {
816 case 'a':
817 pattern += 'a]';
818 break;
819
820 case 'b':
821 pattern += 'ab]';
822 break;
823
824 default:
825 pattern += 'a-' + max[2] + ']';
826 break;
827 }
828
829 break;
830
831 case "hh":
832 pattern += '[' + min[2];
833
834 if (diff > 1) {
835 pattern += '-';
836 }
837
838 pattern += max[2] + ']';
839 break;
840 }
841
842 diff = parseInt(max[2], 16) - parseInt(min[2], 16);
843 min = '' + (parseInt(min, 16) + diff).toString(16);
844 re.push(pattern);
845 } else {
846 diff = max[2] - min[2];
847 pattern = min[0] + min[1];
848
849 switch (diff) {
850 case 0:
851 pattern += min[2];
852 break;
853
854 case 1:
855 pattern += '[' + min[2] + max[2] + ']';
856 break;
857
858 default:
859 pattern += '[' + min[2] + '-' + max[2] + ']';
860 break;
861 }
862
863 min = '' + (min * 1 + diff);
864 re.push(pattern);
865 }
866 }
867
868 pattern = ''; //last place is whole hundred
869
870 if (min[2] == '0' && max[2] == '0') {
871 pattern = max;
872 re.push(pattern);
873 }
874
875 pattern = '';
876 cnt = re.length;
877
878 if (cnt == 1) {
879 pattern = re[0];
880 } else {
881 pattern = re.join(')|(');
882 pattern = '((' + pattern + '))';
883 }
884
885 return pattern;
886};
887
888/**
889 * Function to transform an FSW query string to one or more regular expressions
890 * @function fswquery.regex
891 * @param {string} query - an FSW query string
892 * @returns {string[]} an array of one or more regular expressions
893 * @example
894 * fswquery.regex('QS100uuS20500480x520')
895 *
896 * return [
897 * '(A(S[123][0-9a-f]{2}[0-5][0-9a-f])+)?[BLMR]([0-9]{3}x[0-9]{3})(S[123][0-9a-f]{2}[0-5][0-9a-f][0-9]{3}x[0-9]{3})*S100[0-5][0-9a-f][0-9]{3}x[0-9]{3}(S[123][0-9a-f]{2}[0-5][0-9a-f][0-9]{3}x[0-9]{3})*',
898 * '(A(S[123][0-9a-f]{2}[0-5][0-9a-f])+)?[BLMR]([0-9]{3}x[0-9]{3})(S[123][0-9a-f]{2}[0-5][0-9a-f][0-9]{3}x[0-9]{3})*S20500((4[6-9][0-9])|(500))x((5[0-3][0-9])|(540))(S[123][0-9a-f]{2}[0-5][0-9a-f][0-9]{3}x[0-9]{3})*'
899 * ]
900 */
901
902const regex = query => {
903 query = query.match(new RegExp(`^${re.full}`))[0];
904
905 if (!query) {
906 return '';
907 }
908
909 var matches;
910 var i;
911 var fsw_pattern;
912 var part;
913 var from;
914 var to;
915 var re_range;
916 var segment;
917 var x;
918 var y;
919 var base;
920 var fill;
921 var rotate;
922 var fuzz = 20;
923 var re_sym = 'S[123][0-9a-f]{2}[0-5][0-9a-f]';
924 var re_coord = '[0-9]{3}x[0-9]{3}';
925 var re_word = '[BLMR](' + re_coord + ')(' + re_sym + re_coord + ')*';
926 var re_sortable = '(A(' + re_sym + ')+)';
927 var q_range = 'R[123][0-9a-f]{2}t[123][0-9a-f]{2}';
928 var q_sym = 'S[123][0-9a-f]{2}[0-5u][0-9a-fu]';
929 var q_coord = '([0-9]{3}x[0-9]{3})?';
930 var q_var = '(V[0-9]+)';
931 var q_style = '(' + re$2.full + ')?';
932 var q_sortable;
933
934 if (query == 'Q') {
935 return [re_sortable + "?" + re_word];
936 }
937
938 if (query == 'Q-') {
939 return [re_sortable + "?" + re_word + q_style];
940 }
941
942 if (query == 'QT') {
943 return [re_sortable + re_word];
944 }
945
946 if (query == 'QT-') {
947 return [re_sortable + re_word + q_style];
948 }
949
950 var segments = [];
951 var sortable = query.indexOf('T') + 1;
952
953 if (sortable) {
954 q_sortable = '(A';
955 var qat = query.slice(0, sortable);
956 query = query.replace(qat, '');
957
958 if (qat == 'QT') {
959 q_sortable += '(' + re_sym + ')+)';
960 } else {
961 matches = qat.match(new RegExp('(' + q_sym + '|' + q_range + ')', 'g'));
962
963 if (matches) {
964 var matched;
965
966 for (i = 0; i < matches.length; i += 1) {
967 matched = matches[i].match(new RegExp(q_sym));
968
969 if (matched) {
970 segment = matched[0].slice(0, 4);
971 fill = matched[0].slice(4, 5);
972
973 if (fill == 'u') {
974 segment += '[0-5]';
975 } else {
976 segment += fill;
977 }
978
979 rotate = matched[0].slice(5, 6);
980
981 if (rotate == 'u') {
982 segment += '[0-9a-f]';
983 } else {
984 segment += rotate;
985 }
986
987 q_sortable += segment;
988 } else {
989 from = matches[i].slice(1, 4);
990 to = matches[i].slice(5, 8);
991 re_range = range(from, to, 'hex');
992 segment = 'S' + re_range + '[0-5][0-9a-f]';
993 q_sortable += segment;
994 }
995 }
996
997 q_sortable += '(' + re_sym + ')*)';
998 }
999 }
1000 } //get the variance
1001
1002
1003 matches = query.match(new RegExp(q_var, 'g'));
1004
1005 if (matches) {
1006 fuzz = matches.toString().slice(1) * 1;
1007 } //this gets all symbols with or without location
1008
1009
1010 fsw_pattern = q_sym + q_coord;
1011 matches = query.match(new RegExp(fsw_pattern, 'g'));
1012
1013 if (matches) {
1014 for (i = 0; i < matches.length; i += 1) {
1015 part = matches[i].toString();
1016 base = part.slice(1, 4);
1017 segment = 'S' + base;
1018 fill = part.slice(4, 5);
1019
1020 if (fill == 'u') {
1021 segment += '[0-5]';
1022 } else {
1023 segment += fill;
1024 }
1025
1026 rotate = part.slice(5, 6);
1027
1028 if (rotate == 'u') {
1029 segment += '[0-9a-f]';
1030 } else {
1031 segment += rotate;
1032 }
1033
1034 if (part.length > 6) {
1035 x = part.slice(6, 9) * 1;
1036 y = part.slice(10, 13) * 1; //now get the x segment range+++
1037
1038 segment += range(x - fuzz, x + fuzz);
1039 segment += 'x';
1040 segment += range(y - fuzz, y + fuzz);
1041 } else {
1042 segment += re_coord;
1043 } //now I have the specific search symbol
1044 // add to general ksw word
1045
1046
1047 segment = re_word + segment + '(' + re_sym + re_coord + ')*';
1048
1049 if (sortable) {
1050 segment = q_sortable + segment;
1051 } else {
1052 segment = re_sortable + "?" + segment;
1053 }
1054
1055 if (query.indexOf('-') > 0) {
1056 segment += q_style;
1057 }
1058
1059 segments.push(segment);
1060 }
1061 } //this gets all ranges
1062
1063
1064 fsw_pattern = q_range + q_coord;
1065 matches = query.match(new RegExp(fsw_pattern, 'g'));
1066
1067 if (matches) {
1068 for (i = 0; i < matches.length; i += 1) {
1069 part = matches[i].toString();
1070 from = part.slice(1, 4);
1071 to = part.slice(5, 8);
1072 re_range = range(from, to, "hex");
1073 segment = 'S' + re_range + '[0-5][0-9a-f]';
1074
1075 if (part.length > 8) {
1076 x = part.slice(8, 11) * 1;
1077 y = part.slice(12, 15) * 1; //now get the x segment range+++
1078
1079 segment += range(x - fuzz, x + fuzz);
1080 segment += 'x';
1081 segment += range(y - fuzz, y + fuzz);
1082 } else {
1083 segment += re_coord;
1084 } // add to general ksw word
1085
1086
1087 segment = re_word + segment + '(' + re_sym + re_coord + ')*';
1088
1089 if (sortable) {
1090 segment = q_sortable + segment;
1091 } else {
1092 segment = re_sortable + "?" + segment;
1093 }
1094
1095 if (query.indexOf('-') > 0) {
1096 segment += q_style;
1097 }
1098
1099 segments.push(segment);
1100 }
1101 }
1102
1103 if (!segments.length) {
1104 if (query.indexOf('-') > 0) {
1105 segment += q_style;
1106 }
1107
1108 segments.push(q_sortable + re_word);
1109 }
1110
1111 return segments;
1112};
1113
1114/**
1115 * Function that uses a query string to match signs from a string of text.
1116 * @function fswquery.results
1117 * @param {string} query - an FSW query string
1118 * @param {string} text - a string of text containing multiple signs
1119 * @returns {string[]} an array of FSW signs
1120 * @example
1121 * fswquery.results('QAS10011T','AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 AS15a21S15a07S21100S2df04S2df14M521x538S15a07494x488S15a21498x489S2df04498x517S2df14497x461S21100479x486 AS1f010S10018S20600M519x524S10018485x494S1f010490x494S20600481x476')
1122 *
1123 * return [
1124 * 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
1125 * ]
1126 */
1127
1128const results = (query, text) => {
1129 if (!text) {
1130 return [];
1131 }
1132
1133 let pattern;
1134 let matches;
1135 let parts;
1136 let words;
1137 let re = regex(query);
1138
1139 if (!re) {
1140 return [];
1141 }
1142
1143 let i;
1144
1145 for (i = 0; i < re.length; i += 1) {
1146 pattern = re[i];
1147 matches = text.match(new RegExp(pattern, 'g'));
1148
1149 if (matches) {
1150 text = matches.join(' ');
1151 } else {
1152 text = '';
1153 }
1154 }
1155
1156 if (text) {
1157 parts = text.split(' ');
1158 words = parts.filter(function (element) {
1159 return element in parts ? false : parts[element] = true;
1160 }, {});
1161 } else {
1162 words = [];
1163 }
1164
1165 return words;
1166}; //needs rewritten, but it works
1167
1168/**
1169 * Function that uses an FSW query string to match signs from multiple lines of text.
1170 * @function fswquery.lines
1171 * @param {string} query - an FSW query string
1172 * @param {string} text - multiple lines of text, each starting with an FSW sign
1173 * @returns {string[]} an array of lines of text, each starting with an FSW sign
1174 * @example
1175 * fswquery.lines('QAS10011T',`AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 line one
1176 * AS15a21S15a07S21100S2df04S2df14M521x538S15a07494x488S15a21498x489S2df04498x517S2df14497x461S21100479x486 line two
1177 * AS1f010S10018S20600M519x524S10018485x494S1f010490x494S20600481x476 line three`)
1178 *
1179 * return [
1180 * 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 line one'
1181 * ]
1182 */
1183
1184
1185const lines = (query, text) => {
1186 if (!text) {
1187 return [];
1188 }
1189
1190 let pattern;
1191 let matches;
1192 let parts;
1193 let words;
1194 let re = regex(query);
1195
1196 if (!re) {
1197 return [];
1198 }
1199
1200 let i;
1201
1202 for (i = 0; i < re.length; i += 1) {
1203 pattern = re[i];
1204 pattern = '^' + pattern + '.*';
1205 matches = text.match(new RegExp(pattern, 'mg'));
1206
1207 if (matches) {
1208 text = matches.join("\n");
1209 } else {
1210 text = '';
1211 }
1212 }
1213
1214 if (text) {
1215 parts = text.split("\n");
1216 words = parts.filter(function (element) {
1217 return element in parts ? false : parts[element] = true;
1218 }, {});
1219 } else {
1220 words = [];
1221 }
1222
1223 return words;
1224};
1225
1226export { compose, fsw2query, lines, parse, range, re, regex, results };
1227
1228/* support ongoing development on https://patreon.com/signwriting */