@sutton-signwriting/core
Version:
a javascript package for node and browsers that supports general processing of the Sutton SignWriting script
1,159 lines (1,131 loc) • 34.4 kB
JavaScript
/**
* Sutton SignWriting Core Module v2.0.0 (https://github.com/sutton-signwriting/core)
* Author: Steve Slevinski (https://SteveSlevinski.me)
* fswquery.cjs is released under the MIT License.
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
/**
* Object of regular expressions for FSW query strings
*
* @alias fswquery.re
* @type {object}
* @property {string} null - the null symbol
* @property {string} base - FSW symbol base with neither fill or rotation
* @property {string} coord - FSW coordinate of X and Y values separated by 'x'
* @property {string} var - variance string for searching sign box
* @property {string} symbol - FSW symbol key starting with 'S'
* @property {string} nullorsymbol - null or a symbol
* @property {string} range - FSW range starting with 'R'
* @property {string} item - FSW symbol or range query string
* @property {string} list - several FSW symbols and FSW ranges as a logical OR for searching
* @property {string} prefix - a sequential list of FSW symbol keys with nulls starting with 'A'
* @property {string} signbox - several groups of FSW lists, each group having a coordinate
* @property {string} full - a query string to search prefix in order and the signbox with variance
*/
let re$2 = {
'null': 'S00000',
'base': '[123][0-9a-f]{2}',
'coord': '(?:[0-9]{3}x[0-9]{3})?',
'var': 'V[0-9]+'
};
re$2.symbol = `S${re$2.base}[0-5u][0-9a-fu]`;
re$2.nullorsymbol = `(?:${re$2.null}|${re$2.symbol})`;
re$2.range = `R${re$2.base}t${re$2.base}`;
re$2.item = `(?:${re$2.null}|${re$2.symbol}|${re$2.range})`;
re$2.list = `${re$2.item}(?:o${re$2.item})*`;
re$2.prefix = `(?:A(?:${re$2.list})+)?T`;
re$2.signbox = `(?:${re$2.list}${re$2.coord})*`;
re$2.full = `Q(${re$2.prefix})?(${re$2.signbox})?(${re$2.var})?(-?)`;
/**
* Object of regular expressions for FSW strings
*
* @alias fsw.re
* @property {string} null - the null symbol
* @property {string} symbol - a symbol
* @property {string} nullorsymbol - null or a symbol
* @property {string} sort - the sorting marker
* @property {string} prefix - a sorting marker followed by one or more symbols with nulls
* @property {string} box - a signbox marker
* @property {string} coord - a coordinate
* @property {string} spatial - a symbol followed by a coordinate
* @property {string} signbox - a signbox marker, max coordinate and zero or more spatial symbols
* @property {string} sign - an optional prefix followed by a signbox
* @property {string} sortable - a mandatory prefix followed by a signbox
*/
let re$1 = {
'null': 'S00000',
'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
'coord': '[0-9]{3}x[0-9]{3}',
'sort': 'A',
'box': '[BLMR]'
};
re$1.nullorsymbol = `(?:${re$1.null}|${re$1.symbol})`;
re$1.prefix = `(?:${re$1.sort}${re$1.nullorsymbol}+)`;
re$1.spatial = `${re$1.symbol}${re$1.coord}`;
re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
/** 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.
* [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
* @module convert
*/
/**
* Function to convert an FSW coordinate string to an array of x,y integers
* @function convert.fsw2coord
* @param {string} fswCoord - An FSW coordinate string
* @returns {number[]} Array of x,y integers
* @example
* convert.fsw2coord('500x500')
*
* return [500, 500]
*/
const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
const parsePrefix = text => {
return {
required: true,
parts: text == 'T' ? undefined : text.match(new RegExp(`${re$2.list}`, 'g')).map(part => {
if (part.includes('o')) {
return ['or'].concat(part.match(new RegExp(`(${re$2.item})`, 'g')).map(part => part[0] == 'S' ? part : part.slice(1).split('t')));
} else {
return part[0] == 'S' ? part : part.slice(1).split('t');
}
})
};
};
const parseSignbox = text => {
return text.match(new RegExp(`(${re$2.list}${re$2.coord})`, 'g')).map(part => {
let coord, front;
if (part.includes('x')) {
coord = fsw2coord(part.slice(-7));
front = part.slice(0, -7);
} else {
front = part;
}
if (front.includes('o')) {
return {
or: front.split('o').map(part => {
if (part.includes('S')) {
return part;
} else {
return part.slice(1).split('t');
}
}),
coord,
coord
};
} else if (front.includes('S')) {
return {
symbol: front,
coord: coord
};
} else {
return {
range: front.slice(1).split('t'),
coord: coord
};
}
});
};
/**
* Function to parse FSW query string to object
* @function fswquery.parse
* @param {string} fswQueryString - an FSW query string
* @returns {QueryObject} elements of a of query string identified by regular expression
* @example
* fswquery.parse('QAS10000S10500oS20500oR2fft304TS100uuR205t206oS207uu510x510V5-')
*
* return {
* "query": true,
* "prefix": {
* "required": true,
* "parts": [
* "S10000",
* [
* "or",
* "S10500",
* "S20500",
* [
* "2ff",
* "304"
* ]
* ]
* ]
* },
* "signbox": [
* {
* "symbol": "S100uu"
* },
* {
* "or": [
* [
* "205",
* "206"
* ],
* "S207uu"
* ],
* "coord": [
* 510,
* 510
* ]
* }
* ],
* "variance": 5,
* "style": true
* }
*/
const parse$1 = fswQueryString => {
const query = typeof fswQueryString === 'string' ? fswQueryString.match(new RegExp(`^${re$2.full}`)) : undefined;
return {
'query': query ? true : undefined,
'prefix': query && query[1] ? parsePrefix(query[1]) : undefined,
'signbox': query && query[2] ? parseSignbox(query[2]) : undefined,
'variance': query && query[3] ? parseInt(query[3].slice(1)) : undefined,
'style': query && query[4] ? true : undefined
};
};
/**
* Function to compose FSW query string from object
* @function fswquery.compose
* @param {QueryObject} fswQueryObject - an object of query options
* @returns {string} FSW query string
* @example
* fswquery.compose({
* query: true,
* prefix: {
* required: true,
* parts: [
* 'S10000',
* ['100', '204'],
* 'S20500'
* ]
* },
* signbox: [
* { symbol: 'S20000' },
* {
* range: ['100', '105'],
* coord: [500, 500]
* }
* ],
* variance: 5,
* style: true
* })
*
* return 'QAS10000R100t204S20500TS20000R100t105500x500V5-'
*/
const compose = fswQueryObject => {
if (!fswQueryObject || !fswQueryObject.query) {
return undefined;
}
let query = 'Q';
if (fswQueryObject.prefix && fswQueryObject.prefix.required) {
if (Array.isArray(fswQueryObject.prefix.parts)) {
query += 'A';
query += fswQueryObject.prefix.parts.map(part => {
if (typeof part === 'string') {
return part;
} else {
if (Array.isArray(part) && part.length == 2) {
return `R${part[0]}t${part[1]}`;
} else if (Array.isArray(part) && part.length > 2 && part[0] == 'or') {
part.shift();
return part.map(part => {
if (typeof part === 'string') {
return part;
} else {
if (Array.isArray(part) && part.length == 2) {
return `R${part[0]}t${part[1]}`;
}
}
}).join('o');
}
}
}).join('');
}
query += 'T';
}
if (Array.isArray(fswQueryObject.signbox)) {
query += fswQueryObject.signbox.map(part => {
let out;
if (part.or) {
out = part.or.map(item => {
if (typeof item === 'string') {
return item;
} else {
if (Array.isArray(item) && item.length == 2) {
return `R${item[0]}t${item[1]}`;
}
}
}).join('o');
} else if (part.symbol) {
out = part.symbol;
} else {
if (part.range && Array.isArray(part.range) && part.range.length == 2) {
out = `R${part.range[0]}t${part.range[1]}`;
}
}
return out + (Array.isArray(part.coord) && part.coord.length == 2 ? part.coord.join('x') : '');
}).join('');
}
query += fswQueryObject.style ? '-' : '';
query = query.match(new RegExp(`^${re$2.full}`))[0];
return query;
};
/**
* Object of regular expressions for style strings
*
* @alias style.re
* @type {object}
* @property {string} colorize - regular expression for colorize section
* @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
* @property {string} colorname - regular expression for css color name
* @property {string} padding - regular expression for padding section
* @property {string} zoom - regular expression for zoom section
* @property {string} classbase - regular expression for class name definition
* @property {string} id - regular expression for id definition
* @property {string} colorbase - regular expression for color hex or color name
* @property {string} color - regular expression for single color entry
* @property {string} colors - regular expression for double color entry
* @property {string} background - regular expression for background section
* @property {string} detail - regular expression for color details for line and optional fill
* @property {string} detailsym - regular expression for color details for individual symbols
* @property {string} classes - regular expression for one or more class names
* @property {string} full - full regular expression for style string
*/
let re = {
'colorize': 'C',
'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
'colorname': '[a-zA-Z]+',
'padding': 'P[0-9]{2}',
'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
};
re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
re.color = `_${re.colorbase}_`;
re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
re.background = `G${re.color}`;
re.detail = `D${re.colors}`;
re.detailsym = `D[0-9]{2}${re.colors}`;
re.classes = `${re.classbase}(?: ${re.classbase})*`;
re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
const parse = {
/**
* Function to parse an fsw symbol with optional coordinate and style string
* @function fsw.parse.symbol
* @param {string} fswSym - an fsw symbol
* @returns {SymbolObject} elements of fsw symbol
* @example
* fsw.parse.symbol('S10000500x500-C')
*
* return {
* 'symbol': 'S10000',
* 'coord': [500, 500],
* 'style': '-C'
* }
*/
symbol: fswSym => {
const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.full})?`;
const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
return {
'symbol': symbol ? symbol[1] : undefined,
'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
'style': symbol ? symbol[3] : undefined
};
},
/**
* Function to parse an fsw sign with style string
* @function fsw.parse.sign
* @param {string} fswSign - an fsw sign
* @returns { SignObject } elements of fsw sign
* @example
* fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
*
* return {
* sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
* box: 'M',
* max: [525, 535],
* spatials: [
* {
* symbol: 'S2e748',
* coord: [483, 510]
* },
* {
* symbol: 'S10011',
* coord: [501, 466]
* },
* {
* symbol: 'S2e704',
* coord: [510, 500]
* },
* {
* symbol: 'S10019',
* coord: [476, 475]
* }
* ],
* style: '-C'
* }
*/
sign: fswSign => {
const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re.full})?`;
const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
if (sign) {
return {
'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
'box': sign[2][0],
'max': fsw2coord(sign[2].slice(1, 8)),
'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
return {
symbol: m.slice(0, 6),
coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
};
}),
'style': sign[3]
};
} else {
return {};
}
},
/**
* Function to parse an fsw text
* @function fsw.parse.text
* @param {string} fswText - an fsw text
* @returns {string[]} fsw signs and punctuations
* @example
* fsw.parse.text('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496')
*
* return [
* 'AS14c20S27106M518x529S14c20481x471S27106503x489',
* 'AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468',
* 'S38800464x496'
* ]
*/
text: fswText => {
if (typeof fswText !== 'string') return [];
const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
const matches = fswText.match(new RegExp(regex, 'g'));
return matches ? [...matches] : [];
}
};
/**
* Function to convert an FSW sign to a query string
*
* For the flags parameter, use one or more of the following.
* - A: exact symbol in temporal prefix
* - a: general symbol in temporal prefix
* - S: exact symbol in spatial signbox
* - s: general symbol in spatial signbox
* - L: spatial signbox symbol at location
* @function fswquery.fsw2query
* @param {string} fswSign - FSW sign
* @param {string} flags - flags for query string creation
* @returns {string} FSW query string
* @example
* fswquery.fsw2query('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475', 'ASL')
*
* return 'QAS10011S10019S2e704S2e748TS2e748483x510S10011501x466S2e704510x500S10019476x475'
*/
const fsw2query = (fswSign, flags) => {
let query = '';
const parsed = parse.sign(fswSign);
if (parsed.box) {
const A_flag = flags.indexOf('A') > -1;
const a_flag = flags.indexOf('a') > -1;
const S_flag = flags.indexOf('S') > -1;
const s_flag = flags.indexOf('s') > -1;
const L_flag = flags.indexOf('L') > -1;
if (A_flag || a_flag || S_flag || s_flag) {
if ((A_flag || a_flag) && parsed.sequence) {
query += 'A';
query += parsed.sequence.map(sym => sym.slice(0, 4) + (a_flag ? 'uu' : sym.slice(4, 6))).join('');
query += 'T';
}
if ((S_flag || s_flag) && parsed.spatials) {
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('');
}
}
return query ? "Q" + query : undefined;
} else {
return undefined;
}
};
//needs rewritten, but it works
/**
* Function to transform a range to a regular expression
* @function fswquery.range
* @param {(number|string)} min - either a decimal number or hexidecimal string
* @param {(number|string)} max - either a decimal number or hexidecimal string
* @param {boolean?} hex - if true, the regular expression will match a hexidecimal range
* @returns {string} a regular expression that matches a range
* @example
* fswquery.range(500,750)
*
* return '(([56][0-9][0-9])|(7[0-4][0-9])|(750))'
* @example
* fswquery.range('100','10e',true)
*
* return '10[0-9a-e]'
*/
const range = (min, max, hex) => {
let pattern;
let re;
let diff;
let tmax;
let cnt;
let minV;
let maxV;
if (!hex) {
hex = '';
}
min = ("000" + min).slice(-3);
max = '' + max;
pattern = '';
if (min === max) {
return min;
}
//ending pattern will be series of connected OR ranges
re = [];
//first pattern+ 10's don't match and the min 1's are not zero
//odd number to 9
if (!(min[0] == max[0] && min[1] == max[1])) {
if (min[2] != '0') {
pattern = min[0] + min[1];
if (hex) {
//switch for dex
switch (min[2]) {
case "f":
pattern += 'f';
break;
case "e":
pattern += '[ef]';
break;
case "d":
case "c":
case "b":
case "a":
pattern += '[' + min[2] + '-f]';
break;
default:
switch (min[2]) {
case "9":
pattern += '[9a-f]';
break;
case "8":
pattern += '[89a-f]';
break;
default:
pattern += '[' + min[2] + '-9a-f]';
break;
}
break;
}
diff = 15 - parseInt(min[2], 16) + 1;
min = '' + (parseInt(min, 16) + diff).toString(16);
re.push(pattern);
} else {
//switch for dex
switch (min[2]) {
case "9":
pattern += '9';
break;
case "8":
pattern += '[89]';
break;
default:
pattern += '[' + min[2] + '-9]';
break;
}
diff = 9 - min[2] + 1;
min = '' + (min * 1 + diff);
re.push(pattern);
}
}
}
pattern = '';
//if hundreds are different, get odd to 99 or ff
if (min[0] != max[0]) {
if (min[1] != '0') {
if (hex) {
//scrape to ff
pattern = min[0];
switch (min[1]) {
case "f":
pattern += 'f';
break;
case "e":
pattern += '[ef]';
break;
case "d":
case "c":
case "b":
case "a":
pattern += '[' + min[1] + '-f]';
break;
case "9":
pattern += '[9a-f]';
break;
case "8":
pattern += '[89a-f]';
break;
default:
pattern += '[' + min[1] + '-9a-f]';
break;
}
pattern += '[0-9a-f]';
diff = 15 - parseInt(min[1], 16) + 1;
min = '' + (parseInt(min, 16) + diff * 16).toString(16);
re.push(pattern);
} else {
//scrape to 99
pattern = min[0];
diff = 9 - min[1] + 1;
switch (min[1]) {
case "9":
pattern += '9';
break;
case "8":
pattern += '[89]';
break;
default:
pattern += '[' + min[1] + '-9]';
break;
}
pattern += '[0-9]';
diff = 9 - min[1] + 1;
min = '' + (min * 1 + diff * 10);
re.push(pattern);
}
}
}
pattern = '';
//if hundreds are different, get to same
if (min[0] != max[0]) {
if (hex) {
diff = parseInt(max[0], 16) - parseInt(min[0], 16);
tmax = (parseInt(min[0], 16) + diff - 1).toString(16);
switch (diff) {
case 1:
pattern = min[0];
break;
case 2:
pattern = '[' + min[0] + tmax + ']';
break;
default:
if (parseInt(min[0], 16) > 9) {
minV = 'h';
} else {
minV = 'd';
}
if (parseInt(tmax, 16) > 9) {
maxV = 'h';
} else {
maxV = 'd';
}
switch (minV + maxV) {
case "dd":
pattern += '[' + min[0] + '-' + tmax + ']';
break;
case "dh":
diff = 9 - min[0];
//firs get up to 9
switch (diff) {
case 0:
pattern += '[9';
break;
case 1:
pattern += '[89';
break;
default:
pattern += '[' + min[0] + '-9';
break;
}
switch (tmax[0]) {
case 'a':
pattern += 'a]';
break;
case 'b':
pattern += 'ab]';
break;
default:
pattern += 'a-' + tmax + ']';
break;
}
break;
case "hh":
pattern += '[' + min[0] + '-' + tmax + ']';
break;
}
}
pattern += '[0-9a-f][0-9a-f]';
diff = parseInt(max[0], 16) - parseInt(min[0], 16);
min = '' + (parseInt(min, 16) + diff * 256).toString(16);
re.push(pattern);
} else {
diff = max[0] - min[0];
tmax = min[0] * 1 + diff - 1;
switch (diff) {
case 1:
pattern = min[0];
break;
case 2:
pattern = '[' + min[0] + tmax + ']';
break;
default:
pattern = '[' + min[0] + '-' + tmax + ']';
break;
}
pattern += '[0-9][0-9]';
min = '' + (min * 1 + diff * 100);
re.push(pattern);
}
}
pattern = '';
//if tens are different, get to same
if (min[1] != max[1]) {
if (hex) {
diff = parseInt(max[1], 16) - parseInt(min[1], 16);
tmax = (parseInt(min[1], 16) + diff - 1).toString(16);
pattern = min[0];
switch (diff) {
case 1:
pattern += min[1];
break;
case 2:
pattern += '[' + min[1] + tmax + ']';
break;
default:
if (parseInt(min[1], 16) > 9) {
minV = 'h';
} else {
minV = 'd';
}
if (parseInt(tmax, 16) > 9) {
maxV = 'h';
} else {
maxV = 'd';
}
switch (minV + maxV) {
case "dd":
pattern += '[' + min[1];
if (diff > 1) {
pattern += '-';
}
pattern += tmax + ']';
break;
case "dh":
diff = 9 - min[1];
//firs get up to 9
switch (diff) {
case 0:
pattern += '[9';
break;
case 1:
pattern += '[89';
break;
default:
pattern += '[' + min[1] + '-9';
break;
}
switch (max[1]) {
case 'a':
pattern += ']';
break;
case 'b':
pattern += 'a]';
break;
default:
pattern += 'a-' + (parseInt(max[1], 16) - 1).toString(16) + ']';
break;
}
break;
case "hh":
pattern += '[' + min[1];
if (diff > 1) {
pattern += '-';
}
pattern += (parseInt(max[1], 16) - 1).toString(16) + ']';
break;
}
break;
}
pattern += '[0-9a-f]';
diff = parseInt(max[1], 16) - parseInt(min[1], 16);
min = '' + (parseInt(min, 16) + diff * 16).toString(16);
re.push(pattern);
} else {
diff = max[1] - min[1];
tmax = min[1] * 1 + diff - 1;
pattern = min[0];
switch (diff) {
case 1:
pattern += min[1];
break;
case 2:
pattern += '[' + min[1] + tmax + ']';
break;
default:
pattern += '[' + min[1] + '-' + tmax + ']';
break;
}
pattern += '[0-9]';
min = '' + (min * 1 + diff * 10);
re.push(pattern);
}
}
pattern = '';
//if digits are different, get to same
if (min[2] != max[2]) {
if (hex) {
pattern = min[0] + min[1];
diff = parseInt(max[2], 16) - parseInt(min[2], 16);
if (parseInt(min[2], 16) > 9) {
minV = 'h';
} else {
minV = 'd';
}
if (parseInt(max[2], 16) > 9) {
maxV = 'h';
} else {
maxV = 'd';
}
switch (minV + maxV) {
case "dd":
pattern += '[' + min[2];
if (diff > 1) {
pattern += '-';
}
pattern += max[2] + ']';
break;
case "dh":
diff = 9 - min[2];
//firs get up to 9
switch (diff) {
case 0:
pattern += '[9';
break;
case 1:
pattern += '[89';
break;
default:
pattern += '[' + min[2] + '-9';
break;
}
switch (max[2]) {
case 'a':
pattern += 'a]';
break;
case 'b':
pattern += 'ab]';
break;
default:
pattern += 'a-' + max[2] + ']';
break;
}
break;
case "hh":
pattern += '[' + min[2];
if (diff > 1) {
pattern += '-';
}
pattern += max[2] + ']';
break;
}
diff = parseInt(max[2], 16) - parseInt(min[2], 16);
min = '' + (parseInt(min, 16) + diff).toString(16);
re.push(pattern);
} else {
diff = max[2] - min[2];
pattern = min[0] + min[1];
switch (diff) {
case 0:
pattern += min[2];
break;
case 1:
pattern += '[' + min[2] + max[2] + ']';
break;
default:
pattern += '[' + min[2] + '-' + max[2] + ']';
break;
}
min = '' + (min * 1 + diff);
re.push(pattern);
}
}
pattern = '';
//last place is whole hundred
if (min[2] == '0' && max[2] == '0') {
pattern = max;
re.push(pattern);
}
pattern = '';
cnt = re.length;
if (cnt == 1) {
pattern = re[0];
} else {
pattern = re.join(')|(');
pattern = '((' + pattern + '))';
}
return pattern;
};
const regexSymbol = sym => {
let segment = sym.slice(0, 4);
let fill = sym.slice(4, 5);
if (fill == 'u') {
segment += '[0-5]';
} else {
segment += fill;
}
let rotate = sym.slice(5, 6);
if (rotate == 'u') {
segment += '[0-9a-f]';
} else {
segment += rotate;
}
return segment;
};
const regexRange = symRange => {
let from = symRange.slice(1, 4);
let to = symRange.slice(5, 8);
return 'S' + range(from, to, 'hex') + '[0-5][0-9a-f]';
};
//needs rewritten, but it works
/**
* Function to transform an FSW query string to one or more regular expressions
* @function fswquery.regex
* @param {string} query - an FSW query string
* @returns {string[]} an array of one or more regular expressions
* @example
* fswquery.regex('QS100uuS20500480x520')
*
* return [
* '(?: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})*',
* '(?: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})*'
* ]
*/
const regex = query => {
query = query.match(new RegExp(`^${re$2.full}`))[0];
if (!query) {
return '';
}
var matches;
var matchesOr;
var matched;
var orList;
var i;
var j;
var segment;
var coord;
var x;
var y;
var fuzz = 20;
var q_style = '(?:' + re.full + ')?';
var q_sortable;
if (query == 'Q') {
return [re$1.prefix + "?" + re$1.signbox];
}
if (query == 'Q-') {
return [re$1.prefix + "?" + re$1.signbox + q_style];
}
if (query == 'QT') {
return [re$1.prefix + re$1.signbox];
}
if (query == 'QT-') {
return [re$1.prefix + re$1.signbox + q_style];
}
var segments = [];
var sortable = query.indexOf('T') + 1;
if (sortable) {
q_sortable = '(?:A';
var qat = query.slice(0, sortable);
query = query.replace(qat, '');
if (qat == 'QT') {
q_sortable += re$1.nullorsymbol + '+)';
} else {
matches = qat.match(new RegExp(re$2.list, 'g'));
if (matches) {
for (i = 0; i < matches.length; i += 1) {
orList = [];
matchesOr = matches[i].match(new RegExp(re$2.item, 'g'));
if (matchesOr) {
for (j = 0; j < matchesOr.length; j += 1) {
matched = matchesOr[j].match(new RegExp(re$2.nullorsymbol));
if (matched) {
orList.push(regexSymbol(matched[0]));
} else {
orList.push(regexRange(matchesOr[j]));
}
}
if (orList.length == 1) {
q_sortable += orList[0];
} else {
q_sortable += '(?:' + orList.join('|') + ')';
}
}
}
q_sortable += re$1.nullorsymbol + '*)';
}
}
}
//get the variance
matches = query.match(new RegExp(re$2.var, 'g'));
if (matches) {
fuzz = matches.toString().slice(1) * 1;
}
//this gets all symbols and ranges with or without location
matches = query.match(new RegExp(re$2.list + re$2.coord, 'g'));
if (matches) {
for (i = 0; i < matches.length; i += 1) {
orList = [];
matchesOr = matches[i].match(new RegExp('(' + re$2.symbol + '|' + re$2.range + ')', 'g'));
if (matchesOr) {
for (j = 0; j < matchesOr.length; j += 1) {
matched = matchesOr[j].match(new RegExp(re$2.symbol));
if (matched) {
orList.push(regexSymbol(matched[0]));
} else {
orList.push(regexRange(matchesOr[j]));
}
}
if (orList.length == 1) {
segment = orList[0];
} else {
segment = '(?:' + orList.join('|') + ')';
}
}
if (matches[i].includes('x')) {
coord = fsw2coord(matches[i].slice(-7));
x = coord[0];
y = coord[1];
segment += range(x - fuzz, x + fuzz);
segment += 'x';
segment += range(y - fuzz, y + fuzz);
} else {
segment += re$1.coord;
}
// add to general fsw word
segment = re$1.signbox + segment + '(?:' + re$1.symbol + re$1.coord + ')*';
if (sortable) {
segment = q_sortable + segment;
} else {
segment = re$1.prefix + "?" + segment;
}
if (query.indexOf('-') > 0) {
segment += q_style;
}
segments.push(segment);
}
}
if (!segments.length) {
if (query.indexOf('-') > 0) {
segment += q_style;
}
segments.push(q_sortable + re$1.signbox);
}
return segments;
};
//needs rewritten, but it works
/**
* Function that uses a query string to match signs from a string of text.
* @function fswquery.results
* @param {string} query - an FSW query string
* @param {string} text - a string of text containing multiple signs
* @returns {string[]} an array of FSW signs
* @example
* fswquery.results('QAS10011T','AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 AS15a21S15a07S21100S2df04S2df14M521x538S15a07494x488S15a21498x489S2df04498x517S2df14497x461S21100479x486 AS1f010S10018S20600M519x524S10018485x494S1f010490x494S20600481x476')
*
* return [
* 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
* ]
*/
const results = (query, text) => {
if (!text) {
return [];
}
let pattern;
let matches;
let parts;
let words;
let re = regex(query);
if (!re) {
return [];
}
let i;
for (i = 0; i < re.length; i += 1) {
pattern = re[i];
matches = text.match(new RegExp(pattern, 'g'));
if (matches) {
text = matches.join(' ');
} else {
text = '';
}
}
if (text) {
parts = text.split(' ');
words = parts.filter(function (element) {
return element in parts ? false : parts[element] = true;
}, {});
} else {
words = [];
}
return words;
};
//needs rewritten, but it works
/**
* Function that uses an FSW query string to match signs from multiple lines of text.
* @function fswquery.lines
* @param {string} query - an FSW query string
* @param {string} text - multiple lines of text, each starting with an FSW sign
* @returns {string[]} an array of lines of text, each starting with an FSW sign
* @example
* fswquery.lines('QAS10011T',`AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 line one
* AS15a21S15a07S21100S2df04S2df14M521x538S15a07494x488S15a21498x489S2df04498x517S2df14497x461S21100479x486 line two
* AS1f010S10018S20600M519x524S10018485x494S1f010490x494S20600481x476 line three`)
*
* return [
* 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475 line one'
* ]
*/
const lines = (query, text) => {
if (!text) {
return [];
}
let pattern;
let matches;
let parts;
let words;
let re = regex(query);
if (!re) {
return [];
}
let i;
for (i = 0; i < re.length; i += 1) {
pattern = re[i];
pattern = '^' + pattern + '.*';
matches = text.match(new RegExp(pattern, 'mg'));
if (matches) {
text = matches.join("\n");
} else {
text = '';
}
}
if (text) {
parts = text.split("\n");
words = parts.filter(function (element) {
return element in parts ? false : parts[element] = true;
}, {});
} else {
words = [];
}
return words;
};
exports.compose = compose;
exports.fsw2query = fsw2query;
exports.lines = lines;
exports.parse = parse$1;
exports.range = range;
exports.re = re$2;
exports.regex = regex;
exports.results = results;
/* support ongoing development */
/* https://patreon.com/signwriting */
/* https://donate.sutton-signwriting.io */