UNPKG

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