UNPKG

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