UNPKG

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