UNPKG

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