UNPKG

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