ES6 Plato on Github
Report Home
Summary Display
dictionary-parser.mjs
Maintainability
75.48
Lines of code
291
Difficulty
67.41
Estimated Errors
4.41
Function weight
By Complexity
By SLOC
import latinize from '../node_modules/latinize-to-ascii/latinize.mjs'; import * as tree from './dictionary-tree.mjs'; import {escape2utfSpecial, escapeAll, utfSpecial2escaped} from "./dictionary-escaper.mjs"; export function parse(dictionaryString,options={}) { resetCache(); if(typeof options.idSecPwd === 'undefined') options.idSecPwd = true; if(typeof options.escapeAll === 'undefined') options.escapeAll = 0; if(typeof options.accent === 'undefined') options.accent = 0; if(typeof options.lowerCase === 'undefined') options.lowerCase = 0; if(typeof options.leetSpeak === 'undefined') options.leetSpeak = 0; const escapeAllString = options.escapeAll===1?escapeAll(dictionaryString):options.escapeAll===2?`${dictionaryString}\n${escapeAll(dictionaryString)}`:dictionaryString; const escapedString = escape2utfSpecial(escapeAllString); const unbracketed = bracketsHandler(escapedString); const allLines = unbracketed.split('\n'); const parsedLines = parseAllLines(allLines); const monoLined = `(${parsedLines.join('|')})` .replace(/§void§\|/g,'') .replace(/\|§void§/g,'') .replace(/§void§/g,''); if(monoLined.includes('§infiniteRecursion§')) throw new Error(`Unable to parse : ${utfSpecial2escaped(flattenIt(monoLined))}`); if(!options.idSecPwd) return parseEnd(monoLined,allLines,options); const parts = tree.splitAround('@@',tree.buildTreeStruct(monoLined)); const flatParts = []; if(parts.matching) flatParts.push(parts.matching); if(parts.notMatching) flatParts.push(parts.notMatching); const combined = combineUnspecified2IdSecPwd(flatParts); return parseEnd(combined,allLines,options); } function parseEnd(str,allLines,options) { let theString = syncRefHandler(str,allLines) if(theString.match(/=[^>]+>/)) throw new Error(`Unable to parse : ${utfSpecial2escaped(flattenIt(theString))}`); theString = qtyHandler(theString); if(options.accent===1) theString = zeroAccent(theString); if(options.accent===2) theString = optionalAccent(theString); if(options.lowerCase===1) theString = allCase(theString); if(options.lowerCase===2) theString = optionalCase(theString); if(options.leetSpeak===1) theString = optionalLeetSpeak(theString); if(options.leetSpeak===2) theString = allLeetSpeak(theString); if(options.leetSpeak===3) theString = everyLeetSpeak(theString); //TODO: bigLeet Variants return utfSpecial2escaped(flattenIt(theString)); } function zeroVariant(str,func){ const zero = func(str); if(zero===str) return str; return `(${str}|${zero})`; } function optionalVariant(str,func) { return str.split('').map(func).join(''); } function zeroAccent(string) { return zeroVariant(string,latinize); } function optionalAccent(string) { return optionalVariant(string,zeroAccent); } function allCase(string) { return flattenIt(`(${allCapitalized(string)}|${allUpperCase(string)}|${zeroUpperCase(string)})`); } function optionalCase(string) { return flattenIt(`(${string.toUpperCase()}|${optionalCapitalized(string)})`); } function zeroUpperCase(string) { return zeroVariant(string,str=>str.toLowerCase() ); } function allUpperCase(string) { return zeroVariant(string,str=>str.toUpperCase() ); } function optionalUpperCase(string) { return optionalVariant(string,zeroUpperCase); } function allCapitalized(string) { let str = zeroVariant(flattenIt(string),str=>str.split(' ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ') ); let str2 = zeroVariant(flattenIt(string),str=>str.split(' ').map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join(' ') ); return zeroVariant(str,()=>str2 ); } function optionalCapitalized(string) { let str = string.split(' ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' '); return optionalUpperCase(str); } function zeroLeetSpeak(string) { return zeroVariant(string,fromLeet); } function allLeetSpeak(string) { return zeroVariant(string,toLeet); } function optionalLeetSpeak(string) { return optionalVariant(string,zeroLeetSpeak); } function everyLeetSpeak(string) { return optionalLeetSpeak(optionalVariant(string,allLeetSpeak)); } function replaceList(applyOnThisString,list){ return applyOnThisString.split('').map(chr => list[chr] || chr).join(''); } function replaceUpperKeyList(applyOnThisString,list){ return applyOnThisString.split('').map(chr => list[chr.toUpperCase()] || chr).join(''); } /* function toBigLeet(str){ return replaceUpperKeyList(str,{ 'A': '(4|@|^|∂|q)', 'B': '(8|6|13|ß|!3|/3)', 'C': '(¢|©)', 'D': '(0|?)', 'E': '(3|&|€|£|є|ë)', 'F': '(ƒ|φ)', 'G': '(6|&|9)', 'H': '(#|?)', 'I': '(1|!)', 'J': '(;|¿)', 'K': '(x|X)', 'L': '(1|£)', 'M': '^^', 'N': '/V', 'O': '(0|°|¤)', 'P': '(9|7|?|φ)', 'Q': '0', 'R': '(2|z|Z|Я|®)', 'S': '(5|$|z|§)', 'T': '(7|1|†|+)', 'U': '(v|V|µ)', 'V': '(u|U|µ)', 'W': 'vv', 'X': '(8|×)', 'Y': '(7|j|λ)', 'Z': '(2|%)', }); } function fromBigLeet(str) { //TODO:fromBigLeet } */ function toLeet(str){ return replaceUpperKeyList(str,{ 'O': '0', 'I': '1', 'L': '1', 'R': '2', 'Z': '2', 'E': '3', 'A': '4', // '(4|@)' conflit avec la syntaxe @@ 'S': '(5|$)', 'G': '(6|9)', 'T': '(7|1)', 'Y': '7', 'B': '(8|6)', }); } function fromLeet(str){ return replaceList(str,{ //'@': '(a|A)', conflit avec la syntaxe @@ '$': '(s|S)', '0': '(o|O)', '1': '(i|l|I|L|t|T)', '2': '(r|R|z|Z)', '3': '(e|E)', '4': '(a|A)', '5': '(s|S)', '6': '(g|G|b|B)', '7': '(t|T|y|Y)', '8': '(b|B)', '9': '(g|G)', }); } function flattenIt(regStr){ return tree.rawSerialize(tree.buildTreeStruct(regStr)); } function resetCache() { cache(); } function cache(key, func, ...args) { /* Init */ if (!cache.cached) cache.cached = {}; /* Reset cache */ if (arguments.length === 0) return cache.cached = {}; // eslint-disable-line no-return-assign /* Compute and cache */ if (typeof cache.cached[key] === 'undefined') cache.cached[key] = func(...args); /* Answer from cache */ return cache.cached[key]; } function bracketsHandler(theString) { // Handle [] const lower = 'abcdefghijklmnopqrstuvwxyz'; const upper = lower.toUpperCase(); const number = '0123456789'; theString = theString.replace(/(\[[^\]]*)([a-z]-[a-z])([^\]]*])/g, (osef, before, chrs, after) => before + lower.slice(lower.indexOf(chrs.split('-')[0]), lower.indexOf(chrs.split('-')[1]) + 1) + after); theString = theString.replace(/(\[[^\]]*)([A-Z]-[A-Z])([^\]]*])/g, (osef, before, chrs, after) => before + upper.slice(upper.indexOf(chrs.split('-')[0]), upper.indexOf(chrs.split('-')[1]) + 1) + after); theString = theString.replace(/(\[[^\]]*)([0-9]-[0-9])([^\]]*])/g, (osef, before, chrs, after) => before + number.slice(number.indexOf(chrs.split('-')[0]), number.indexOf(chrs.split('-')[1]) + 1) + after); // eslint-disable-line unicorn/better-regex theString = theString.replace(/\[([^\]]+)]/g, (osef, chrs) => `(${chrs.split('').join('|')})`); return theString; } function qtyHandlerReplaceCallback(all, chr, qty) { const mm = qty.split(',').map(n => n.trim() * 1); // eslint-disable-line no-implicit-coercion const min = mm[0]; const max = (mm.length === 2) ? mm[1] : min; let result = new Array(min + 1).join(chr);// eslint-disable-line unicorn/no-new-array for (let i = min; i < max; i++) result += `(|${chr})`; return result; } function qtyHandler(theString) { // Handle {qty} and {min,max} theString = theString.replace(/([^)]){([^}]+)}/g, qtyHandlerReplaceCallback); theString = theString.replace(/^(.*)\(([^)]*)\){([^}]+)}(.*)$/, (all, before, choices, qty, after) => before + qtyHandlerReplaceCallback('', `(${choices})`, qty) + after); // eslint-disable-line max-params while(1){ const qtyFound = rightParser(theString,'{','}'); if(!qtyFound.before || !qtyFound.inside) break; const qtyPerimeter = rightParser(qtyFound.before,'(',')'); theString = qtyPerimeter.before + qtyHandlerReplaceCallback('',`(${qtyPerimeter.inside})`,qtyFound.inside)+qtyFound.after; } return theString; } function rightParser(fullString,openLeftChr,closeRightChr) { const chrList = fullString.split(''); const res = { before: '', inside: '', after: '' }; function insideParser(){ while (chrList.length) { const chr = chrList.pop(); res.inside = chr+res.inside; if(chr===openLeftChr) return; if(chr===closeRightChr) insideParser(); } } while (chrList.length) { const chr = chrList.pop(); if(chr!==closeRightChr) res.after = chr+res.after; if(chr===closeRightChr) { insideParser(); res.inside = res.inside.substring(1); res.before = chrList.join(''); return res; } } return res; } function parseAllLines(lines) { return lines.map(line=>parseLine(line,lines)); } function parseLine(theLine, allLines,labelBreadCrumb='>') { if(theLine.includes('::')) return '§void§'; theLine = refHandler(theLine, allLines, labelBreadCrumb) return theLine; } function refHandler(theString, allStrings, labelBreadCrumb) { // Handle <ref> theString = theString.replace(/<([^>]+)>/g, (osef, ref) => computedLabel(ref, allStrings, labelBreadCrumb)); return theString; } function syncRefHandler(theString, allStrings, labelBreadCrumb='>') { // Handle =ref> const syncRef = {}; theString.replace(/=([^>]+)>/g,(osef,ref)=> syncRef[ref] = computedLabel(ref, allStrings, labelBreadCrumb)); Object.keys(syncRef).forEach(ref=>{ const altList = []; const altTree = tree.buildTreeStruct(syncRef[ref]); const treeSize = tree.altCount(altTree); for(let altIndex = 0; altIndex<treeSize; altIndex++){ const currentAlt = tree.getAlternative(altIndex,altTree); altList.push(theString.replace(new RegExp(`=${ref}>`,'g'),currentAlt)); } theString = flattenIt(`(${altList.join('|')})`); }); return theString; } function computedLabel(label, allStrings, labelBreadCrumb) { return cache(`label::${label}`, _computedLabel, label, allStrings,labelBreadCrumb); } function _computedLabel(label, allStrings, labelBreadCrumb) { if(labelBreadCrumb.includes(label)) return '§infiniteRecursion§'; labelBreadCrumb+=label+'>'; const matchingLabel = allStrings.filter(str => str.trim().indexOf(`${label}::`) === 0); return flattenIt(`(${matchingLabel.map(str => parseLine(labelExpressions(str), allStrings,labelBreadCrumb)).join('|')})`); } function labelExpressions(str) { return str.slice(str.indexOf('::') + 2); } function combineUnspecified2IdSecPwd(lines) { const idSecPwdLines = []; const unspecifiedLines = []; lines.forEach(line => line.includes('@@') ? idSecPwdLines.push(line) : unspecifiedLines.push(line)); if (unspecifiedLines.length) idSecPwdLines.push(`${mergeLines(unspecifiedLines)}@@${mergeLines(unspecifiedLines)}`) return mergeLines(idSecPwdLines); } function mergeLines(lines) { if (!lines.length) return ''; lines = lines.filter(l => l.length); if (lines.length === 1) return lines[0]; return `(${lines.join('|')})`; }