UNPKG

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