compromise-speech 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- var e,t;e=this,t=function(){var e={dedup:e=>e.replace(/([^c])\1/g,"$1"),dropInitialLetters:e=>e.match(/^(kn|gn|pn|ae|wr)/)?e.substring(1,e.length-1):e,dropBafterMAtEnd:e=>e.replace(/mb$/,"m"),cchange:e=>(e=(e=(e=e.replace(/([^s]|^)(c)(h)/g,"$1x$3").trim()).replace(/cia/g,"xia")).replace(/c([iey])/g,"s$1")).replace(/c/g,"k"),dchange:e=>(e=e.replace(/d(ge|gy|gi)/g,"j$1")).replace(/d/g,"t"),dropG:e=>(e=e.replace(/gh(^$|[^aeiou])/g,"h$1")).replace(/g(n|ned)$/g,"$1"),changeG:e=>(e=(e=(e=e.replace(/gh/g,"f")).replace(/([^g]|^)(g)([iey])/g,"$1j$3")).replace(/gg/g,"g")).replace(/g/g,"k"),dropH:e=>e.replace(/([aeiou])h([^aeiou]|$)/g,"$1$2"),changeCK:e=>e.replace(/ck/g,"k"),changePH:e=>e.replace(/ph/g,"f"),changeQ:e=>e.replace(/q/g,"k"),changeS:e=>e.replace(/s(h|io|ia)/g,"x$1"),changeT:e=>e=e.replace(/t(ia[^n]|io)/g,"x$1"),dropT:e=>e.replace(/tch/g,"ch"),changeV:e=>e.replace(/v/g,"f"),changeWH:e=>e.replace(/^wh/,"w"),dropW:e=>e.replace(/w([^aeiou]|$)/g,"$1"),changeX:e=>(e=e.replace(/^x/,"s")).replace(/x/g,"ks"),dropY:e=>e.replace(/y([^aeiou]|$)/g,"$1"),changeZ:e=>e.replace(/z/,"s"),dropVowels:e=>e},t=function(t){return t=e.dedup(t),t=e.dropInitialLetters(t),t=e.dropBafterMAtEnd(t),t=e.changeCK(t),t=e.cchange(t),t=e.dchange(t),t=e.dropG(t),t=e.changeG(t),t=e.dropH(t),t=e.changePH(t),t=e.changeQ(t),t=e.changeS(t),t=e.changeX(t),t=e.changeT(t),t=e.dropT(t),t=e.changeV(t),t=e.changeWH(t),t=e.dropW(t),t=e.dropY(t),t=e.changeZ(t),(t=e.dropVowels(t)).trim()};const a=/^(eu)/i,c=/^[^aeiou]e([^d]|$)/,n=/^([^aeiouy])[aeiouy]\1[aeiouy]/,l=/^[^aeiouy]([aeiouy])[^aeiouy]\1/,h=/^([tg][aeiouy]){2}/,o=/^[^aeiouy]+$/,i=/[aeiouy]$/,r=/^[^aeiouy]h?[aeiouy]/,g=[/^[^aeiou]?ion/,/^[^aeiou]?ised/,/^[^aeiou]?iled/,/[aeiou]n[gt]$/,/\wa[gt]e$/];var p=function(e){let t=(e=(e=e.map((function(e){return e.trim()}))).filter((function(e){return""!==e}))).length;if(t>1){let a=e[t-2]+e[t-1];for(let c=0;c<g.length;c++)a.match(g[c])&&(e[t-2]=e[t-2]+e[t-1],e.pop())}if(e.length>1){let t=(1===e[0].length||e[0].match(r))&&e[0].match(i),a=e[1].match(c);if(t&&a){let t=e[0]+e[1];t.match(n)||t.match(l)||t.match(h)||(e[0]=e[0]+e[1],e.splice(1,1))}}if(e.length>1){let t=e[e.length-2].match(r)&&e[e.length-2].match(i),a=e[e.length-1].match(c)&&g.every(t=>!e[e.length-1].match(t));if(t&&a){let t=e[e.length-2]+e[e.length-1];t.match(n)||t.match(l)||t.match(h)||(e[e.length-2]=e[e.length-2]+e[e.length-1],e.splice(e.length-1,1))}}if(e.length>1){let t=e[0]+e[1];t.match(a)&&(e[0]=t,e.splice(1,1))}return e.length>1&&e[e.length-1].match(o)&&(e[e.length-2]=e[e.length-2]+e[e.length-1],e.splice(e.length-1,1)),e};const u=/ +/g,s=/[aeiouy]$/,f=/^[^aeiouy]h?[aeiouy]/,d=/^e[sm]/,m=/^e/,y=/(eo|eu|ia|oa|ua|ui)$/i,$=/[aiouy]/,k=/ee$/,b=function(e){let t=[],a=e.split(""),c="",n="",l="";for(let e=0;e<a.length;e++){c=a.slice(0,e).join(""),l=a[e],n=a.slice(e+1,a.length).join("");let h=c+a[e];if(c.match(s)&&!l.match(s))return n.match(d)&&(h+="e",n=n.replace(m,"")),t.push(h),t.concat(b(n));if(h.match(y))return t.push(c),t.push(l),t.concat(b(n));if(h.match(s)&&n.match(f))return t.push(h),t.concat(b(n))}if(e.match($)||e.match(k))t.push(e);else if(e){let a=t.length-1;a<0&&(a=0),t[a]=(t[a]||"")+e}return t};var x=function(e){let t=[];return e?((e=e.replace(/[.,?]/g,"")).split(u).map(e=>{t=t.concat(b(e))}),t=p(t),0===t.length&&(t=[e]),t=t.filter(e=>e),t):t};return{api:function(e){e.prototype.syllables=function(){this.compute("syllables");let e=[];return this.docs.forEach(t=>{let a=[];t.forEach(e=>{a=a.concat(e.syllables)}),a.length>0&&e.push(a)}),e},e.prototype.soundsLike=function(){this.compute("soundsLike");let e=[];return this.docs.forEach(t=>{let a=[];t.forEach(e=>{a=a.concat(e.soundsLike)}),a.length>0&&e.push(a)}),e}},compute:{soundsLike:function(e){e.docs.forEach(e=>{e.forEach(e=>{e.soundsLike=t(e.normal||e.text)})})},syllables:function(e){e.docs.forEach(e=>{e.forEach(e=>{e.syllables=x(e.normal||e.text)})})}}}},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).compromiseSpeech=t();
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).compromiseSpeech=t()}(this,function(){"use strict";const e={acre:[1],cafe:[2],abalone:[2,3,5],aborigine:[2,3,6,7],comanche:[2,5],dolce:[3],acacia:[1,3],fuchsia:[4],concierge:[3,5],concierges:[3,5],comedienne:[2,4,6],massacre:[3,5],massacred:[3,5],massacring:[3,5],aisle:[],isle:[],wednesday:[6],buoyant:[4],buoyancy:[4,6],acquiesce:[2,5],acquiesced:[2,5],acquiescent:[2,5,7],acquiescence:[2,5,7],people:[3],queue:[],business:[4],quiet:[3],science:[3],poet:[2],poetry:[2,4],every:[3],everything:[3,5],everybody:[3,5,7],ilyich:[2],juan:[],wyatt:[2],batesville:[5],belleville:[5],bridgeport:[6],cambridgeport:[3,9],cambridgeside:[3,9],janesville:[5],roseberry:[4,7],roseville:[4],belge:[3],canadienne:[2,4,6],mildred:[3],nietzsche:[5],odea:[1,3],zoologist:[2,4,5],zoology:[2,4,5]},t=t=>{if(Object.hasOwn(e,t))return e[t];const a=t.replace(/s$/,"");return Object.hasOwn(e,a)?/[csxz]e$/.test(a)?e[a].concat(a.length-2):e[a]:null},a=[/^note(?=book)/,/^side(?=kick|line|show|step|stream|track|walk|water|way|winder|wise)/,/^some(?=bod|one|time|where)/,/^none(?=such)/,/^goose(?=berr)/,/^above(?=board|ground)/,/^a?fore(?=clos|taste|said|mentioned)/,/^ware(?=house|housing)/,/^space(?=port|ship)/,/^race(?=horse)/,/^lake(?=side)/,/^white(?=side)/,/^state(?=side)/,/^juke(?=box)/,/^police(?=woman|women|man|men)/,/^(?:sale|spoke)s?(?=woman|women|man|men)/],s=/[aeiouy]/,o=[{match:/[^aeiou]e(?=['’](?:s|d|ll|re|ve)$|$)/g,offset:1,except:/[^aeiouyl]le(?:['’](?:s|d|ll|re|ve))?$/},{match:/[^td]ed(?=['’](?:s|d|ll|re|ve)$|$)/g,offset:1,except:/(?:[^aeiouyl]led|^(?:hundred|kindred|sacred|wicked|rugged|ragged|wretched|naked|crooked|(?:a|em|death|river|water|flower|hot|sick|sun|sea)bed|(?:in|inter|thorough|cross|pure)bred|infrared|newlywed|premed))(?:['’](?:s|d|ll|re|ve))?$/},{match:/es(?=['’](?:s|d|ll|re|ve)$|$)/g,offset:0,except:/([sxzcg]|ch|sh|[^aeiouyl]l)es(?:['’](?:s|d|ll|re|ve))?$/},{match:/e(?=ments?$|thing$|s?man$)/g,offset:0,except:/(?:[^aeiouyl]le(?:ments?|thing|s?man)|elements?)$/},{match:/[^aeiou]e(?=ness(?:es)?$|l(?:y|iness)$|ful(?:ly)?$|less$|ships?$|some$)/g,offset:1,except:/[^aeiouyl]le(?:ness(?:es)?|l(?:y|iness)|ful(?:ly)?|less|ships?|some)$/},{match:/['’][rv]e$/g,offset:2},{match:/qu(?=[aeiouy])/g,offset:1},{match:/que(?=s?$|ly$|ness$)/g,offset:2},{match:/^y(?=[aeiou])/g,offset:0},{match:/[aeiouwy]y(?=[aeiou])/g,offset:1},{match:/[aeiou]ny(?=[aeiou])/g,offset:2,except:/^any/},{match:/gu(?=a)/g,offset:1},{match:/busi(?=ness)/g,offset:3},{match:/ever(?=y(?:body|thing|where|one|day|time|$))/g,offset:2}],n=/^mc(?=[a-z])/,i=[/(?:[sxz]e?|[cs]h|[cg]e)['’](s)$/,/[ai]s(m)(?:s|['’]s)?$/,/[iy]th(m)(?:s|['’]s)?$/,/[^aeiouyn](n)['’]t$/,/^(?:could|should|would|might|must)['’](v)e$/],c=[/^(?:re)?cre(?=at[eio])/,/^re(?=act)/,/^re(?=absorb|app|elect|emerg|enter|establish|evalu|impos|invent|issue|us[ei])/,/^pre(?=adolesc|ambl|arrang|eminen|empt|exist|ignition|invas)/,/^(?:micro|macro)(?=[aeiou])/,/^co(?=operat|ordinat|alesc|erc)/,/^pro(?=act)/,/^flu(?=id)/,/^ru(?=in)/,/di(?=et|en[ct])/,/^ide(?=a)/,/^sovi(?=et)/,/^soci(?=et)/,/i(?=ell[aoi])/,/i(?=et(?:te)?s?$)/,/^qui(?=et)/,/^acre(?=age)/,/^sci(?=on)/,/^intra(?=[aeiou])/,/re(?=al(?!ly|m))/,/[^q]u(?=en[ctdz])/,/[^g]u(?=ing)/,/argu(?=ing)/,/^lou(?=is)/,/[eo]u(?=illes?$)/,/ou(?=ett)/,/o(?=as?$)/],r=[{before:1,after:1,match:/^(ai|au|ay|ea|ee|ei|eu|ey|ie|oa|oe|oi|oo|ou|oy|ue|ui)$/},{before:3,after:2,match:/^llion/},{before:2,after:3,match:/^[cst]i(?:on|an|al|ous)/},{before:2,after:4,match:/^cia(?:s|['’]s)?$/},{before:2,after:1,match:/^eau$/},{before:1,after:3,match:/^ae(?:r|st)/},{before:2,after:3,match:/^[cg]eous/},{before:3,after:3,match:/^hteous/}],l=new Set(["bl","br","ch","cl","cr","dr","fl","fr","gl","gr","ph","pl","pr","qu","sh","sk","sl","sm","sn","sp","st","sw","th","tr","tw","wh","scr","shr","spl","spr","str","thr"]),h=[{minGap:1,maxGap:1,gap:/^x$/,left:1},{minGap:2,gap:/^(.)\1/,minLeft:1},{minGap:2,tail:/^ed$/,right:-1},{minGap:2,gap:/^sh$/,tail:/^(es|ing)$/,right:0},{minGap:2,gap:/l$/,tail:/^e(?:[sd]|['’](?:s|d|ll|re|ve))?$/,right:-2},{minGap:2,gap:/l$/,tail:/^e(?:ments?|ships?|ness(?:es)?|less|ful(?:ly)?|ly)(?:['’]s)?$/,right:-2},{minGap:1,gap:/l$/,tail:/^ess(?:ly|ness(?:es)?)?$/,right:-1},{minGap:3,gap:/^ssl$/,tail:/^y$/,right:-1},{minGap:1,gap:/^(?:s|th)$/,tail:/^m(?:s|['’]s)?$/,right:0},{minGap:1,tail:/^n['’]t$/,right:0},{minGap:1,gap:/^[-‐‑–—]+$/,right:0}],u=e=>{const u=t(e);if(null!==u)return u;const g=((e,t)=>{const s=[],o=a.flatMap(t=>{const a=t.exec(e);return a?[a[0].length]:[]});for(let a=1;a<t.length;a+=1){const n=t[a-1].end,i=t[a].start,c=e.slice(n,i),r=e.slice(i);let u=n;if(c.length>1){u=i-1;for(let e=Math.min(3,c.length);e>1;e-=1)if(l.has(c.slice(-e))){u=i-e;break}}h.forEach(e=>{c.length<e.minGap||void 0!==e.maxGap&&c.length>e.maxGap||e.gap&&!e.gap.test(c)||e.tail&&!e.tail.test(r)||(u=void 0!==e.left?n+e.left:void 0!==e.right?i+e.right:Math.max(u,n+e.minLeft))}),o.forEach(e=>{e>=n&&e<=i&&(u=e)}),s.push(u)}return s})(e,(e=>{const t=new Set;a.forEach(a=>{const s=a.exec(e);s&&t.add(s[0].lastIndexOf("e"))}),o.forEach(a=>{if(!a.except||!a.except.test(e))for(const s of e.matchAll(a.match))t.add(s.index+a.offset)});const l=new Set;c.forEach(t=>{const a=t.exec(e);a&&l.add(a.index+a[0].length)});const h=[],u=n.exec(e);u&&h.push({start:0,end:u[0].length});const g=new Set;i.forEach(t=>{const a=t.exec(e);a&&g.add(a.index+a[0].lastIndexOf(a[1]))});for(let a=0;a<e.length;a+=1){if(!s.test(e[a])&&!g.has(a)||t.has(a))continue;const o=h[h.length-1];o&&o.end===a&&!l.has(a)&&r.some(t=>t.match.test(e.slice(Math.max(0,a-t.before),a+t.after)))?o.end=a+1:h.push({start:a,end:a+1})}return h})(e)),p=e.replace(/['’](?:s|d|ll|re|ve)$/,"");if(p===e)return g;const f=t(p);return null!==f?f.concat(g.filter(e=>e>=p.length)):g},g=/(?<!\p{Punctuation})\p{Punctuation}+$/u,p=/^[\p{Punctuation}$]+/u,f=(e,t={})=>{if(!e.text)return[];let a=e.text;t.normalize&&(a=e.normal||e.text);const{clean:s,prefix:o}=(e=>{let t=e.toLowerCase();t=t.replace(g,"");const a=t.match(p)?.[0]||"";return{clean:t.slice(a.length),prefix:a}})(a);let n=u(s);return o&&o.length>0&&(n=((e,t="")=>e.map(e=>e+t.length))(n,o)),((e,t)=>{let a=0;const s=t.map(t=>{const s=e.slice(a,t);return a=t,s});return s.push(e.slice(a)),s})(a,n)},d=e=>e.replace(/([^c])\1/g,"$1"),m=e=>/^(kn|gn|pn|ae|wr)/.test(e)?e.substring(1,e.length-1):e,$=e=>e.replace(/mb$/,"m"),y=e=>(e=(e=(e=e.replace(/([^s]|^)(c)(h)/g,"$1x$3").trim()).replace(/cia/g,"xia")).replace(/c([iey])/g,"s$1")).replace(/c/g,"k"),b=e=>(e=e.replace(/d(ge|gy|gi)/g,"j$1")).replace(/d/g,"t"),x=e=>(e=e.replace(/gh(^$|[^aeiou])/g,"h$1")).replace(/g(n|ned)$/g,"$1"),w=e=>(e=(e=(e=e.replace(/gh/g,"f")).replace(/([^g]|^)(g)([iey])/g,"$1j$3")).replace(/gg/g,"g")).replace(/g/g,"k"),v=e=>e.replace(/([aeiou])h([^aeiou]|$)/g,"$1$2"),k=e=>e.replace(/ck/g,"k"),G=e=>e.replace(/ph/g,"f"),E=e=>e.replace(/q/g,"k"),q=e=>e.replace(/s(h|io|ia)/g,"x$1"),z=e=>e=e.replace(/t(ia[^n]|io)/g,"x$1"),j=e=>e.replace(/tch/g,"ch"),L=e=>e.replace(/v/g,"f"),O=e=>e.replace(/^wh/,"w"),S=e=>e.replace(/w([^aeiou]|$)/g,"$1"),M=e=>(e=e.replace(/^x/,"s")).replace(/x/g,"ks"),P=e=>e.replace(/y([^aeiou]|$)/g,"$1"),I=e=>e.replace(/z/,"s"),T=e=>e;return{version:"0.2.0",api:function(e){e.prototype.syllables=function(e={}){this.compute("syllables",e);const t=[];return this.docs.forEach(e=>{let a=[];e.forEach(e=>{a=a.concat(e.syllables)}),a.length>0&&t.push(a)}),t},e.prototype.soundsLike=function(){this.compute("soundsLike");const e=[];return this.docs.forEach(t=>{let a=[];t.forEach(e=>{a=a.concat(e.soundsLike)}),a.length>0&&e.push(a)}),e}},compute:{syllables:function(e,t={}){e.docs.forEach(e=>{e.forEach(e=>{e.syllables=f(e,t)})})},soundsLike:function(e){e.docs.forEach(e=>{e.forEach(e=>{var t;e.soundsLike=(t=e.normal||e.text,t=d(t),t=m(t),t=$(t),t=k(t),t=y(t),t=b(t),t=x(t),t=w(t),t=v(t),t=G(t),t=E(t),t=q(t),t=M(t),t=z(t),t=j(t),t=L(t),t=O(t),t=S(t),t=P(t),t=I(t),(t=T(t)).trim())})})}}}});
@@ -1 +1 @@
1
- var e={dedup:e=>e.replace(/([^c])\1/g,"$1"),dropInitialLetters:e=>e.match(/^(kn|gn|pn|ae|wr)/)?e.substring(1,e.length-1):e,dropBafterMAtEnd:e=>e.replace(/mb$/,"m"),cchange:e=>(e=(e=(e=e.replace(/([^s]|^)(c)(h)/g,"$1x$3").trim()).replace(/cia/g,"xia")).replace(/c([iey])/g,"s$1")).replace(/c/g,"k"),dchange:e=>(e=e.replace(/d(ge|gy|gi)/g,"j$1")).replace(/d/g,"t"),dropG:e=>(e=e.replace(/gh(^$|[^aeiou])/g,"h$1")).replace(/g(n|ned)$/g,"$1"),changeG:e=>(e=(e=(e=e.replace(/gh/g,"f")).replace(/([^g]|^)(g)([iey])/g,"$1j$3")).replace(/gg/g,"g")).replace(/g/g,"k"),dropH:e=>e.replace(/([aeiou])h([^aeiou]|$)/g,"$1$2"),changeCK:e=>e.replace(/ck/g,"k"),changePH:e=>e.replace(/ph/g,"f"),changeQ:e=>e.replace(/q/g,"k"),changeS:e=>e.replace(/s(h|io|ia)/g,"x$1"),changeT:e=>e=e.replace(/t(ia[^n]|io)/g,"x$1"),dropT:e=>e.replace(/tch/g,"ch"),changeV:e=>e.replace(/v/g,"f"),changeWH:e=>e.replace(/^wh/,"w"),dropW:e=>e.replace(/w([^aeiou]|$)/g,"$1"),changeX:e=>(e=e.replace(/^x/,"s")).replace(/x/g,"ks"),dropY:e=>e.replace(/y([^aeiou]|$)/g,"$1"),changeZ:e=>e.replace(/z/,"s"),dropVowels:e=>e};var a=function(a){return a=e.dedup(a),a=e.dropInitialLetters(a),a=e.dropBafterMAtEnd(a),a=e.changeCK(a),a=e.cchange(a),a=e.dchange(a),a=e.dropG(a),a=e.changeG(a),a=e.dropH(a),a=e.changePH(a),a=e.changeQ(a),a=e.changeS(a),a=e.changeX(a),a=e.changeT(a),a=e.dropT(a),a=e.changeV(a),a=e.changeWH(a),a=e.dropW(a),a=e.dropY(a),a=e.changeZ(a),(a=e.dropVowels(a)).trim()};const t=/^(eu)/i,c=/^[^aeiou]e([^d]|$)/,n=/^([^aeiouy])[aeiouy]\1[aeiouy]/,l=/^[^aeiouy]([aeiouy])[^aeiouy]\1/,h=/^([tg][aeiouy]){2}/,r=/^[^aeiouy]+$/,o=/[aeiouy]$/,g=/^[^aeiouy]h?[aeiouy]/,i=[/^[^aeiou]?ion/,/^[^aeiou]?ised/,/^[^aeiou]?iled/,/[aeiou]n[gt]$/,/\wa[gt]e$/];var p=function(e){let a=(e=(e=e.map((function(e){return e.trim()}))).filter((function(e){return""!==e}))).length;if(a>1){let t=e[a-2]+e[a-1];for(let c=0;c<i.length;c++)t.match(i[c])&&(e[a-2]=e[a-2]+e[a-1],e.pop())}if(e.length>1){let a=(1===e[0].length||e[0].match(g))&&e[0].match(o),t=e[1].match(c);if(a&&t){let a=e[0]+e[1];a.match(n)||a.match(l)||a.match(h)||(e[0]=e[0]+e[1],e.splice(1,1))}}if(e.length>1){let a=e[e.length-2].match(g)&&e[e.length-2].match(o),t=e[e.length-1].match(c)&&i.every(a=>!e[e.length-1].match(a));if(a&&t){let a=e[e.length-2]+e[e.length-1];a.match(n)||a.match(l)||a.match(h)||(e[e.length-2]=e[e.length-2]+e[e.length-1],e.splice(e.length-1,1))}}if(e.length>1){let a=e[0]+e[1];a.match(t)&&(e[0]=a,e.splice(1,1))}return e.length>1&&e[e.length-1].match(r)&&(e[e.length-2]=e[e.length-2]+e[e.length-1],e.splice(e.length-1,1)),e};const u=/ +/g,s=/[aeiouy]$/,d=/^[^aeiouy]h?[aeiouy]/,f=/^e[sm]/,m=/^e/,$=/(eo|eu|ia|oa|ua|ui)$/i,y=/[aiouy]/,k=/ee$/,E=function(e){let a=[],t=e.split(""),c="",n="",l="";for(let e=0;e<t.length;e++){c=t.slice(0,e).join(""),l=t[e],n=t.slice(e+1,t.length).join("");let h=c+t[e];if(c.match(s)&&!l.match(s))return n.match(f)&&(h+="e",n=n.replace(m,"")),a.push(h),a.concat(E(n));if(h.match($))return a.push(c),a.push(l),a.concat(E(n));if(h.match(s)&&n.match(d))return a.push(h),a.concat(E(n))}if(e.match(y)||e.match(k))a.push(e);else if(e){let t=a.length-1;t<0&&(t=0),a[t]=(a[t]||"")+e}return a};var x=function(e){let a=[];return e?((e=e.replace(/[.,?]/g,"")).split(u).map(e=>{a=a.concat(E(e))}),a=p(a),0===a.length&&(a=[e]),a=a.filter(e=>e),a):a};var b={api:function(e){e.prototype.syllables=function(){this.compute("syllables");let e=[];return this.docs.forEach(a=>{let t=[];a.forEach(e=>{t=t.concat(e.syllables)}),t.length>0&&e.push(t)}),e},e.prototype.soundsLike=function(){this.compute("soundsLike");let e=[];return this.docs.forEach(a=>{let t=[];a.forEach(e=>{t=t.concat(e.soundsLike)}),t.length>0&&e.push(t)}),e}},compute:{soundsLike:function(e){e.docs.forEach(e=>{e.forEach(e=>{e.soundsLike=a(e.normal||e.text)})})},syllables:function(e){e.docs.forEach(e=>{e.forEach(e=>{e.syllables=x(e.normal||e.text)})})}}};export{b as default};
1
+ const e={acre:[1],cafe:[2],abalone:[2,3,5],aborigine:[2,3,6,7],comanche:[2,5],dolce:[3],acacia:[1,3],fuchsia:[4],concierge:[3,5],concierges:[3,5],comedienne:[2,4,6],massacre:[3,5],massacred:[3,5],massacring:[3,5],aisle:[],isle:[],wednesday:[6],buoyant:[4],buoyancy:[4,6],acquiesce:[2,5],acquiesced:[2,5],acquiescent:[2,5,7],acquiescence:[2,5,7],people:[3],queue:[],business:[4],quiet:[3],science:[3],poet:[2],poetry:[2,4],every:[3],everything:[3,5],everybody:[3,5,7],ilyich:[2],juan:[],wyatt:[2],batesville:[5],belleville:[5],bridgeport:[6],cambridgeport:[3,9],cambridgeside:[3,9],janesville:[5],roseberry:[4,7],roseville:[4],belge:[3],canadienne:[2,4,6],mildred:[3],nietzsche:[5],odea:[1,3],zoologist:[2,4,5],zoology:[2,4,5]},t=t=>{if(Object.hasOwn(e,t))return e[t];const a=t.replace(/s$/,"");return Object.hasOwn(e,a)?/[csxz]e$/.test(a)?e[a].concat(a.length-2):e[a]:null},a=[/^note(?=book)/,/^side(?=kick|line|show|step|stream|track|walk|water|way|winder|wise)/,/^some(?=bod|one|time|where)/,/^none(?=such)/,/^goose(?=berr)/,/^above(?=board|ground)/,/^a?fore(?=clos|taste|said|mentioned)/,/^ware(?=house|housing)/,/^space(?=port|ship)/,/^race(?=horse)/,/^lake(?=side)/,/^white(?=side)/,/^state(?=side)/,/^juke(?=box)/,/^police(?=woman|women|man|men)/,/^(?:sale|spoke)s?(?=woman|women|man|men)/],s=/[aeiouy]/,r=[{match:/[^aeiou]e(?=['’](?:s|d|ll|re|ve)$|$)/g,offset:1,except:/[^aeiouyl]le(?:['’](?:s|d|ll|re|ve))?$/},{match:/[^td]ed(?=['’](?:s|d|ll|re|ve)$|$)/g,offset:1,except:/(?:[^aeiouyl]led|^(?:hundred|kindred|sacred|wicked|rugged|ragged|wretched|naked|crooked|(?:a|em|death|river|water|flower|hot|sick|sun|sea)bed|(?:in|inter|thorough|cross|pure)bred|infrared|newlywed|premed))(?:['’](?:s|d|ll|re|ve))?$/},{match:/es(?=['’](?:s|d|ll|re|ve)$|$)/g,offset:0,except:/([sxzcg]|ch|sh|[^aeiouyl]l)es(?:['’](?:s|d|ll|re|ve))?$/},{match:/e(?=ments?$|thing$|s?man$)/g,offset:0,except:/(?:[^aeiouyl]le(?:ments?|thing|s?man)|elements?)$/},{match:/[^aeiou]e(?=ness(?:es)?$|l(?:y|iness)$|ful(?:ly)?$|less$|ships?$|some$)/g,offset:1,except:/[^aeiouyl]le(?:ness(?:es)?|l(?:y|iness)|ful(?:ly)?|less|ships?|some)$/},{match:/['’][rv]e$/g,offset:2},{match:/qu(?=[aeiouy])/g,offset:1},{match:/que(?=s?$|ly$|ness$)/g,offset:2},{match:/^y(?=[aeiou])/g,offset:0},{match:/[aeiouwy]y(?=[aeiou])/g,offset:1},{match:/[aeiou]ny(?=[aeiou])/g,offset:2,except:/^any/},{match:/gu(?=a)/g,offset:1},{match:/busi(?=ness)/g,offset:3},{match:/ever(?=y(?:body|thing|where|one|day|time|$))/g,offset:2}],o=/^mc(?=[a-z])/,c=[/(?:[sxz]e?|[cs]h|[cg]e)['’](s)$/,/[ai]s(m)(?:s|['’]s)?$/,/[iy]th(m)(?:s|['’]s)?$/,/[^aeiouyn](n)['’]t$/,/^(?:could|should|would|might|must)['’](v)e$/],n=[/^(?:re)?cre(?=at[eio])/,/^re(?=act)/,/^re(?=absorb|app|elect|emerg|enter|establish|evalu|impos|invent|issue|us[ei])/,/^pre(?=adolesc|ambl|arrang|eminen|empt|exist|ignition|invas)/,/^(?:micro|macro)(?=[aeiou])/,/^co(?=operat|ordinat|alesc|erc)/,/^pro(?=act)/,/^flu(?=id)/,/^ru(?=in)/,/di(?=et|en[ct])/,/^ide(?=a)/,/^sovi(?=et)/,/^soci(?=et)/,/i(?=ell[aoi])/,/i(?=et(?:te)?s?$)/,/^qui(?=et)/,/^acre(?=age)/,/^sci(?=on)/,/^intra(?=[aeiou])/,/re(?=al(?!ly|m))/,/[^q]u(?=en[ctdz])/,/[^g]u(?=ing)/,/argu(?=ing)/,/^lou(?=is)/,/[eo]u(?=illes?$)/,/ou(?=ett)/,/o(?=as?$)/],i=[{before:1,after:1,match:/^(ai|au|ay|ea|ee|ei|eu|ey|ie|oa|oe|oi|oo|ou|oy|ue|ui)$/},{before:3,after:2,match:/^llion/},{before:2,after:3,match:/^[cst]i(?:on|an|al|ous)/},{before:2,after:4,match:/^cia(?:s|['’]s)?$/},{before:2,after:1,match:/^eau$/},{before:1,after:3,match:/^ae(?:r|st)/},{before:2,after:3,match:/^[cg]eous/},{before:3,after:3,match:/^hteous/}],l=new Set(["bl","br","ch","cl","cr","dr","fl","fr","gl","gr","ph","pl","pr","qu","sh","sk","sl","sm","sn","sp","st","sw","th","tr","tw","wh","scr","shr","spl","spr","str","thr"]),h=[{minGap:1,maxGap:1,gap:/^x$/,left:1},{minGap:2,gap:/^(.)\1/,minLeft:1},{minGap:2,tail:/^ed$/,right:-1},{minGap:2,gap:/^sh$/,tail:/^(es|ing)$/,right:0},{minGap:2,gap:/l$/,tail:/^e(?:[sd]|['’](?:s|d|ll|re|ve))?$/,right:-2},{minGap:2,gap:/l$/,tail:/^e(?:ments?|ships?|ness(?:es)?|less|ful(?:ly)?|ly)(?:['’]s)?$/,right:-2},{minGap:1,gap:/l$/,tail:/^ess(?:ly|ness(?:es)?)?$/,right:-1},{minGap:3,gap:/^ssl$/,tail:/^y$/,right:-1},{minGap:1,gap:/^(?:s|th)$/,tail:/^m(?:s|['’]s)?$/,right:0},{minGap:1,tail:/^n['’]t$/,right:0},{minGap:1,gap:/^[-‐‑–—]+$/,right:0}],g=e=>{const g=t(e);if(null!==g)return g;const u=((e,t)=>{const s=[],r=a.flatMap(t=>{const a=t.exec(e);return a?[a[0].length]:[]});for(let a=1;a<t.length;a+=1){const o=t[a-1].end,c=t[a].start,n=e.slice(o,c),i=e.slice(c);let g=o;if(n.length>1){g=c-1;for(let e=Math.min(3,n.length);e>1;e-=1)if(l.has(n.slice(-e))){g=c-e;break}}h.forEach(e=>{n.length<e.minGap||void 0!==e.maxGap&&n.length>e.maxGap||e.gap&&!e.gap.test(n)||e.tail&&!e.tail.test(i)||(g=void 0!==e.left?o+e.left:void 0!==e.right?c+e.right:Math.max(g,o+e.minLeft))}),r.forEach(e=>{e>=o&&e<=c&&(g=e)}),s.push(g)}return s})(e,(e=>{const t=new Set;a.forEach(a=>{const s=a.exec(e);s&&t.add(s[0].lastIndexOf("e"))}),r.forEach(a=>{if(!a.except||!a.except.test(e))for(const s of e.matchAll(a.match))t.add(s.index+a.offset)});const l=new Set;n.forEach(t=>{const a=t.exec(e);a&&l.add(a.index+a[0].length)});const h=[],g=o.exec(e);g&&h.push({start:0,end:g[0].length});const u=new Set;c.forEach(t=>{const a=t.exec(e);a&&u.add(a.index+a[0].lastIndexOf(a[1]))});for(let a=0;a<e.length;a+=1){if(!s.test(e[a])&&!u.has(a)||t.has(a))continue;const r=h[h.length-1];r&&r.end===a&&!l.has(a)&&i.some(t=>t.match.test(e.slice(Math.max(0,a-t.before),a+t.after)))?r.end=a+1:h.push({start:a,end:a+1})}return h})(e)),p=e.replace(/['’](?:s|d|ll|re|ve)$/,"");if(p===e)return u;const f=t(p);return null!==f?f.concat(u.filter(e=>e>=p.length)):u},u=/(?<!\p{Punctuation})\p{Punctuation}+$/u,p=/^[\p{Punctuation}$]+/u,f=(e,t={})=>{if(!e.text)return[];let a=e.text;t.normalize&&(a=e.normal||e.text);const{clean:s,prefix:r}=(e=>{let t=e.toLowerCase();t=t.replace(u,"");const a=t.match(p)?.[0]||"";return{clean:t.slice(a.length),prefix:a}})(a);let o=g(s);return r&&r.length>0&&(o=((e,t="")=>e.map(e=>e+t.length))(o,r)),((e,t)=>{let a=0;const s=t.map(t=>{const s=e.slice(a,t);return a=t,s});return s.push(e.slice(a)),s})(a,o)},m=e=>e.replace(/([^c])\1/g,"$1"),d=e=>/^(kn|gn|pn|ae|wr)/.test(e)?e.substring(1,e.length-1):e,$=e=>e.replace(/mb$/,"m"),y=e=>(e=(e=(e=e.replace(/([^s]|^)(c)(h)/g,"$1x$3").trim()).replace(/cia/g,"xia")).replace(/c([iey])/g,"s$1")).replace(/c/g,"k"),b=e=>(e=e.replace(/d(ge|gy|gi)/g,"j$1")).replace(/d/g,"t"),x=e=>(e=e.replace(/gh(^$|[^aeiou])/g,"h$1")).replace(/g(n|ned)$/g,"$1"),w=e=>(e=(e=(e=e.replace(/gh/g,"f")).replace(/([^g]|^)(g)([iey])/g,"$1j$3")).replace(/gg/g,"g")).replace(/g/g,"k"),v=e=>e.replace(/([aeiou])h([^aeiou]|$)/g,"$1$2"),k=e=>e.replace(/ck/g,"k"),G=e=>e.replace(/ph/g,"f"),E=e=>e.replace(/q/g,"k"),q=e=>e.replace(/s(h|io|ia)/g,"x$1"),z=e=>e=e.replace(/t(ia[^n]|io)/g,"x$1"),L=e=>e.replace(/tch/g,"ch"),j=e=>e.replace(/v/g,"f"),O=e=>e.replace(/^wh/,"w"),M=e=>e.replace(/w([^aeiou]|$)/g,"$1"),S=e=>(e=e.replace(/^x/,"s")).replace(/x/g,"ks"),P=e=>e.replace(/y([^aeiou]|$)/g,"$1"),I=e=>e.replace(/z/,"s"),A=e=>e;var C={version:"0.2.0",api:function(e){e.prototype.syllables=function(e={}){this.compute("syllables",e);const t=[];return this.docs.forEach(e=>{let a=[];e.forEach(e=>{a=a.concat(e.syllables)}),a.length>0&&t.push(a)}),t},e.prototype.soundsLike=function(){this.compute("soundsLike");const e=[];return this.docs.forEach(t=>{let a=[];t.forEach(e=>{a=a.concat(e.soundsLike)}),a.length>0&&e.push(a)}),e}},compute:{syllables:function(e,t={}){e.docs.forEach(e=>{e.forEach(e=>{e.syllables=f(e,t)})})},soundsLike:function(e){e.docs.forEach(e=>{e.forEach(e=>{var t;e.soundsLike=(t=e.normal||e.text,t=m(t),t=d(t),t=$(t),t=k(t),t=y(t),t=b(t),t=x(t),t=w(t),t=v(t),t=G(t),t=E(t),t=q(t),t=S(t),t=z(t),t=L(t),t=j(t),t=O(t),t=M(t),t=P(t),t=I(t),(t=A(t)).trim())})})}}};export{C as default};
package/package.json CHANGED
@@ -1,38 +1,59 @@
1
1
  {
2
2
  "name": "compromise-speech",
3
- "description": "plugin for nlp-compromise",
4
- "version": "0.1.0",
5
- "author": "Spencer Kelly <spencermountain@gmail.com> (http://spencermounta.in)",
6
- "main": "./src/plugin.js",
7
- "unpkg": "./builds/compromise-speech.min.js",
8
- "module": "./builds/compromise-speech.mjs",
3
+ "version": "0.2.0",
4
+ "description": "Syllable counting and pronunciation estimates for compromise",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/spencermountain/compromise.git",
9
+ "directory": "plugins/speech"
10
+ },
11
+ "homepage": "https://github.com/spencermountain/compromise/tree/master/plugins/speech",
12
+ "author": "Spencer Kelly <spencermountain@gmail.com> (https://spencermountain.dev)",
9
13
  "type": "module",
10
- "types": "index.d.ts",
11
- "sideEffects": false,
12
14
  "exports": {
13
15
  ".": {
14
- "import": "./src/plugin.js",
15
- "require": "./builds/compromise-speech.cjs"
16
+ "import": {
17
+ "types": "./types/index.d.ts",
18
+ "default": "./src/plugin.js"
19
+ },
20
+ "require": {
21
+ "types": "./types/index.d.cts",
22
+ "default": "./builds/compromise-speech.cjs"
23
+ }
16
24
  }
17
25
  },
18
- "repository": {
19
- "type": "git",
20
- "url": "git://github.com/spencermountain/compromise.git"
21
- },
22
- "homepage": "https://github.com/spencermountain/compromise/tree/master/plugins/speech",
23
- "scripts": {
24
- "test": "tape \"./tests/**/*.test.js\" | tap-dancer --color always",
25
- "testb": "TESTENV=prod tape \"./tests/**/*.test.js\" | tap-dancer --color always",
26
- "watch": "amble ./scratch.js",
27
- "build": "rollup -c --silent"
26
+ "main": "./builds/compromise-speech.cjs",
27
+ "module": "./builds/compromise-speech.mjs",
28
+ "types": "./types/index.d.ts",
29
+ "sideEffects": false,
30
+ "engines": {
31
+ "node": ">=18"
28
32
  },
29
- "eslintIgnore": [
30
- "builds/*.js"
33
+ "files": [
34
+ "types/",
35
+ "builds/",
36
+ "src/"
31
37
  ],
38
+ "devDependencies": {
39
+ "@rollup/plugin-node-resolve": "16.0.3",
40
+ "@rollup/plugin-terser": "1.0.0",
41
+ "rollup": "4.64.8",
42
+ "rollup-plugin-filesize-check": "2.0.1",
43
+ "tape": "5.10.3",
44
+ "cross-env": "10.1.0",
45
+ "tap-dancer": "0.4.4"
46
+ },
32
47
  "peerDependencies": {
33
- "compromise": ">=14.0.0"
48
+ "compromise": "^14.19.0"
34
49
  },
35
- "dependencies": {},
36
- "devDependencies": {},
37
- "license": "MIT"
50
+ "unpkg": "./builds/compromise-speech.min.js",
51
+ "scripts": {
52
+ "test": "tape \"./tests/**/*.test.js\" | tap-dancer --color always",
53
+ "testb": "cross-env TESTENV=prod tape \"./tests/**/*.test.js\" | tap-dancer --color always",
54
+ "test:hmm": "tape \"./tests/**/*.hmm.js\" | tap-dancer --color always",
55
+ "watch": "node --watch ./scratch.js",
56
+ "version": "node ./scripts/version.js",
57
+ "build": "node ./scripts/version.js && rollup -c --silent"
58
+ }
38
59
  }
@@ -0,0 +1 @@
1
+ export default '0.2.0'
package/src/api.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const api = function (View) {
2
2
  /** */
3
- View.prototype.syllables = function () {
4
- this.compute('syllables')
5
- let all = []
3
+ View.prototype.syllables = function (options = {}) {
4
+ this.compute('syllables', options)
5
+ const all = []
6
6
  this.docs.forEach(terms => {
7
7
  let some = []
8
8
  terms.forEach(term => {
@@ -17,7 +17,7 @@ const api = function (View) {
17
17
  /** */
18
18
  View.prototype.soundsLike = function () {
19
19
  this.compute('soundsLike')
20
- let all = []
20
+ const all = []
21
21
  this.docs.forEach(terms => {
22
22
  let some = []
23
23
  terms.forEach(term => {
@@ -30,4 +30,4 @@ const api = function (View) {
30
30
  return all
31
31
  }
32
32
  }
33
- export default api
33
+ export default api
package/src/plugin.js CHANGED
@@ -1,7 +1,13 @@
1
- import compute from './compute/index.js'
1
+ import version from './_version.js'
2
2
  import api from './api.js'
3
+ import syllables from './syllables/index.js'
4
+ import soundsLike from './sounds-like/index.js'
3
5
 
4
6
  export default {
7
+ version,
5
8
  api,
6
- compute
7
- }
9
+ compute: {
10
+ syllables,
11
+ soundsLike,
12
+ },
13
+ }
@@ -4,7 +4,7 @@ const transformations = {
4
4
  return s.replace(/([^c])\1/g, '$1')
5
5
  },
6
6
  dropInitialLetters: (s) => {
7
- if (s.match(/^(kn|gn|pn|ae|wr)/)) {
7
+ if (/^(kn|gn|pn|ae|wr)/.test(s)) {
8
8
  return s.substring(1, s.length - 1)
9
9
  }
10
10
  return s
@@ -0,0 +1,12 @@
1
+ import splitTerm from './split/index.js'
2
+
3
+ // Store syllables on each term; the public API collects them by sentence.
4
+ const syllables = function (view, options = {}) {
5
+ view.docs.forEach(terms => {
6
+ terms.forEach(term => {
7
+ term.syllables = splitTerm(term, options)
8
+ })
9
+ })
10
+ }
11
+
12
+ export default syllables
@@ -0,0 +1,32 @@
1
+ const trailingPunct = /(?<!\p{Punctuation})\p{Punctuation}+$/u
2
+ const leadingPunct = /^[\p{Punctuation}$]+/u
3
+
4
+ // Prepare spelling for rule matching; cuts are still applied to the original text.
5
+ const normalize = input => {
6
+ let txt = input.toLowerCase()
7
+ // Ignore trailing punctuation runs for analysis; internal apostrophes stay intact.
8
+ txt = txt.replace(trailingPunct, '')
9
+ // Keep the prefix length so cuts still align with the original spelling.
10
+ const prefix = txt.match(leadingPunct)?.[0] || ''
11
+ const clean = txt.slice(prefix.length)
12
+ return { clean, prefix }
13
+ }
14
+
15
+ const pushCutsBack = (cuts, prefix = '') => {
16
+ return cuts.map(cut => cut + prefix.length)
17
+ }
18
+
19
+ // split a string into chunks based on the given cuts
20
+ const applyCuts = (text, cuts) => {
21
+ let start = 0
22
+ const parts = cuts.map(end => {
23
+ const part = text.slice(start, end)
24
+ start = end
25
+ return part
26
+ })
27
+ // Cuts are interior boundaries; the final chunk always runs to the text's end.
28
+ parts.push(text.slice(start))
29
+ return parts
30
+ }
31
+
32
+ export { applyCuts, normalize, pushCutsBack }
@@ -0,0 +1,81 @@
1
+ // Whole-word splits that override the vowel-span and boundary rules.
2
+ // Keys are lowercase; values are interior cut offsets, excluding the word's end.
3
+ const exceptions = {
4
+ acre: [1], // a-cre: the final re carries a syllable
5
+ // Borrowed words whose final e is pronounced.
6
+ cafe: [2], // ca-fe
7
+ abalone: [2, 3, 5], // ab-a-lo-ne
8
+ aborigine: [2, 3, 6, 7], // ab-o-rig-i-ne
9
+ comanche: [2, 5], // co-man-che
10
+ dolce: [3], // dol-ce
11
+ // Borrowed vowel combinations do not follow the usual English pair rules.
12
+ acacia: [1, 3], // a-ca-cia
13
+ fuchsia: [4], // fuch-sia
14
+ concierge: [3, 5], // con-ci-erge
15
+ concierges: [3, 5], // con-ci-erges
16
+ comedienne: [2, 4, 6], // co-me-di-enne
17
+ // The final re in massacre carries a syllable, including its inflected forms.
18
+ massacre: [3, 5], // mas-sa-cre
19
+ massacred: [3, 5], // mas-sa-cred
20
+ massacring: [3, 5], // mas-sa-cring
21
+ // Silent consonants make these look like syllabic -le endings.
22
+ aisle: [],
23
+ isle: [],
24
+ wednesday: [6], // wednes-day
25
+ // Keep the spoken vowel groups together or apart explicitly.
26
+ buoyant: [4], // buoy-ant
27
+ buoyancy: [4, 6], // buoy-an-cy
28
+ acquiesce: [2, 5], // ac-qui-esce
29
+ acquiesced: [2, 5], // ac-qui-esced
30
+ acquiescent: [2, 5, 7], // ac-qui-es-cent
31
+ acquiescence: [2, 5, 7], // ac-qui-es-cence
32
+ people: [3], //'peo ple',
33
+ queue: [], //'queue',
34
+ business: [4], //'busi ness',
35
+ quiet: [3], //'qui et',
36
+ science: [3], //'sci ence',
37
+ poet: [2], //'po et',
38
+ poetry: [2, 4], //'po et ry',
39
+ every: [3], //'eve ry',
40
+ everything: [3, 5], // 'eve ry thing',
41
+ everybody: [3, 5, 7], //'eve ry bo dy',
42
+ ilyich: [2], // 'il yich': consonantal y in this name
43
+ juan: [], // 'juan':
44
+ wyatt: [2], // 'wy att'
45
+ // Place names retain their recognizable compound boundaries.
46
+ batesville: [5], // bates-ville
47
+ belleville: [5], // belle-ville
48
+ bridgeport: [6], // bridge-port
49
+ cambridgeport: [3, 9], // cam-bridge-port
50
+ cambridgeside: [3, 9], // cam-bridge-side
51
+ janesville: [5], // janes-ville
52
+ roseberry: [4, 7], // rose-ber-ry
53
+ roseville: [4], // rose-ville
54
+ // These names retain vowels that the general spelling rules suppress or blend.
55
+ belge: [3], // bel-ge
56
+ canadienne: [2, 4, 6], // ca-na-di-enne
57
+ mildred: [3], // mil-dred
58
+ nietzsche: [5], // nietz-sche
59
+ odea: [1, 3], // o-de-a
60
+ // The two written o's belong to separate syllables in these words.
61
+ zoologist: [2, 4, 5], // zo-ol-o-gist
62
+ zoology: [2, 4, 5], // zo-ol-o-gy
63
+ }
64
+
65
+ const checkExceptions = str => {
66
+ if (Object.hasOwn(exceptions, str)) {
67
+ return exceptions[str]
68
+ }
69
+ // Reuse singular cuts for a plain -s ending; exact entries above take priority.
70
+ const singular = str.replace(/s$/, '')
71
+ if (Object.hasOwn(exceptions, singular)) {
72
+ // A sibilant before silent e adds a syllable: sci-ence becomes sci-en-ces.
73
+ if (/[csxz]e$/.test(singular)) {
74
+ return exceptions[singular].concat(singular.length - 2)
75
+ }
76
+ return exceptions[singular]
77
+ }
78
+ return null
79
+ }
80
+
81
+ export default checkExceptions
@@ -0,0 +1,155 @@
1
+ import compoundPrefixes from './_compound-prefixes.js'
2
+
3
+ // Roots ending in ed keep their vowel; these are not regular past-tense suffixes.
4
+ const voicedEd = /(?:[^aeiouyl]led|^(?:hundred|kindred|sacred|wicked|rugged|ragged|wretched|naked|crooked|(?:a|em|death|river|water|flower|hot|sick|sun|sea)bed|(?:in|inter|thorough|cross|pure)bred|infrared|newlywed|premed))(?:['’](?:s|d|ll|re|ve))?$/
5
+
6
+ // Find vowel spans in lowercase spelling; return character offsets, not syllables.
7
+ // Candidate vowel letters; ignored rules handle consonantal uses of u and y.
8
+ const isVowel = /[aeiouy]/
9
+
10
+ // Ignore the character at match.index + offset. Rules may veto a whole word.
11
+ // Global patterns visit every occurrence; except prevents that rule from applying.
12
+ const ignored = [
13
+ { match: /[^aeiou]e(?=['’](?:s|d|ll|re|ve)$|$)/g, offset: 1, except: /[^aeiouyl]le(?:['’](?:s|d|ll|re|ve))?$/ }, // silent e
14
+ { match: /[^td]ed(?=['’](?:s|d|ll|re|ve)$|$)/g, offset: 1, except: voicedEd }, // walked vs hundred/bubbled
15
+ { match: /es(?=['’](?:s|d|ll|re|ve)$|$)/g, offset: 0, except: /([sxzcg]|ch|sh|[^aeiouyl]l)es(?:['’](?:s|d|ll|re|ve))?$/ }, // cakes vs boxes/tables
16
+ {
17
+ match: /e(?=ments?$|thing$|s?man$)/g,
18
+ offset: 0,
19
+ except: /(?:[^aeiouyl]le(?:ments?|thing|s?man)|elements?)$/,
20
+ }, // movement(s), but settlement(s) and element(s)
21
+ // Silent e stays silent before consonant suffixes, but syllabic -le survives.
22
+ {
23
+ match: /[^aeiou]e(?=ness(?:es)?$|l(?:y|iness)$|ful(?:ly)?$|less$|ships?$|some$)/g,
24
+ offset: 1,
25
+ except: /[^aeiouyl]le(?:ness(?:es)?|l(?:y|iness)|ful(?:ly)?|less|ships?|some)$/,
26
+ },
27
+ { match: /['’][rv]e$/g, offset: 2 }, // we're, they've: suffix e adds no syllable
28
+ { match: /qu(?=[aeiouy])/g, offset: 1 }, // consonantal u
29
+ { match: /que(?=s?$|ly$|ness$)/g, offset: 2 }, // opaque, opaques, opaquely
30
+ { match: /^y(?=[aeiou])/g, offset: 0 }, // yard, yellow
31
+ { match: /[aeiouwy]y(?=[aeiou])/g, offset: 1 }, // beyond, lawyer, doubled y
32
+ { match: /[aeiou]ny(?=[aeiou])/g, offset: 2, except: /^any/ }, // canyon, but anyone
33
+ { match: /gu(?=a)/g, offset: 1 }, // guard, language
34
+ { match: /busi(?=ness)/g, offset: 3 }, // businesses, agribusiness: the i stays silent
35
+ { match: /ever(?=y(?:body|thing|where|one|day|time|$))/g, offset: 2 }, // everywhere, everyone
36
+ ]
37
+
38
+ // Mc contributes a spoken syllable despite having no written vowel.
39
+ const syllabicPrefix = /^mc(?=[a-z])/
40
+
41
+ // These consonants carry a syllable despite having no written vowel.
42
+ // The captured consonant starts the span; surrounding letters provide context.
43
+ const syllabicConsonants = [
44
+ /(?:[sxz]e?|[cs]h|[cg]e)['’](s)$/, // horse's, church's
45
+ /[ai]s(m)(?:s|['’]s)?$/, // chasm, criticism, and plurals/possessives
46
+ /[iy]th(m)(?:s|['’]s)?$/, // algorithm, rhythms
47
+ /[^aeiouyn](n)['’]t$/, // couldn't, wasn't; not can't or hasn't
48
+ /^(?:could|should|would|might|must)['’](v)e$/, // modal + have: would-'ve, unlike we've
49
+ ]
50
+
51
+ // Keep a boundary after these matched prefixes, even inside a vowel group.
52
+ const separate = [
53
+ /^(?:re)?cre(?=at[eio])/, // create, recreate, recreation (but not creature)
54
+ /^re(?=act)/, // re-act and re-action
55
+ /^re(?=absorb|app|elect|emerg|enter|establish|evalu|impos|invent|issue|us[ei])/, // reuse, reenter; not reed
56
+ /^pre(?=adolesc|ambl|arrang|eminen|empt|exist|ignition|invas)/, // preempt, preexisting; not preen
57
+ /^(?:micro|macro)(?=[aeiou])/, // micro-organism, macro-economic
58
+ /^co(?=operat|ordinat|alesc|erc)/, // cooperate, coercion; not cooperage
59
+ /^pro(?=act)/, // proactive; not approach
60
+ /^flu(?=id)/, // flu-id, unlike fruit
61
+ /^ru(?=in)/, // ru-in and ru-ined
62
+ /di(?=et|en[ct])/, // diet, audience, obedient; not died or soldier
63
+ /^ide(?=a)/, // idea, ideal; not deal
64
+ /^sovi(?=et)/, // Soviet and its derivatives retain separate i and e sounds
65
+ /^soci(?=et)/, // society, societies, societal; not social
66
+ /i(?=ell[aoi])/, // Gabriella, Gabrielli, -iello names; not field
67
+ /i(?=et(?:te)?s?$)/, // Juliet, Henriette
68
+ /^qui(?=et)/, // quiet keeps its separate vowels in quietly and quieter
69
+ /^acre(?=age)/, // acreage retains the vowel of acre before -age
70
+ /^sci(?=on)/, // scion has two vowels, unlike the ending in vision
71
+ /^intra(?=[aeiou])/, // intrauterine, intraocular
72
+ /re(?=al(?!ly|m))/, // cereal, reality; not really or realm
73
+ /[^q]u(?=en[ctdz])/, // fluent, innuendo, influenza; not subsequent
74
+ /[^g]u(?=ing)/, // suing, continuing; not plaguing or intriguing
75
+ /argu(?=ing)/, // arguing retains its pronounced u
76
+ /^lou(?=is)/, // Louis, Louisville
77
+ /[eo]u(?=illes?$)/, // separate the vowel groups in -euille/-ouille
78
+ /ou(?=ett)/, // silhouette, pirouette: ou and e are separate vowels
79
+ /o(?=as?$)/, // boa, boas; not boat
80
+ ]
81
+
82
+ // Match a bounded window around the current vowel: [i - before, i + after).
83
+ const joined = [
84
+ // Common vowel pairs share a span unless a separate rule overrides them.
85
+ { before: 1, after: 1, match: /^(ai|au|ay|ea|ee|ei|eu|ey|ie|oa|oe|oi|oo|ou|oy|ue|ui)$/ },
86
+ { before: 3, after: 2, match: /^llion/ }, // million, unlike lion
87
+ { before: 2, after: 3, match: /^[cst]i(?:on|an|al|ous)/ }, // nation, special, musician
88
+ { before: 2, after: 4, match: /^cia(?:s|['’]s)?$/ }, // final -cia blends; association and glaciation keep separate vowels
89
+ { before: 2, after: 1, match: /^eau$/ }, // extend ea to eau in beautiful
90
+ { before: 1, after: 3, match: /^ae(?:r|st)/ }, // aerospace, aesthetic
91
+ { before: 2, after: 3, match: /^[cg]eous/ }, // gorgeous, herbaceous; not courteous
92
+ { before: 3, after: 3, match: /^hteous/ }, // righteous and its suffixes
93
+ ]
94
+
95
+ // Mark pronounced vowel groups before choosing any consonant boundaries.
96
+ const vowelSpan = word => {
97
+ // Record ignored positions without deleting letters or shifting offsets.
98
+ const silent = new Set()
99
+ // A silent e remains silent inside a recognized compound.
100
+ compoundPrefixes.forEach(pattern => {
101
+ const match = pattern.exec(word)
102
+ if (match) {
103
+ silent.add(match[0].lastIndexOf('e'))
104
+ }
105
+ })
106
+ ignored.forEach(rule => {
107
+ if (!rule.except || !rule.except.test(word)) {
108
+ for (const match of word.matchAll(rule.match)) {
109
+ silent.add(match.index + rule.offset)
110
+ }
111
+ }
112
+ })
113
+ // Forced separations take precedence over the joined-vowel rules below.
114
+ const breaks = new Set()
115
+ separate.forEach(pattern => {
116
+ const match = pattern.exec(word)
117
+ if (match) {
118
+ breaks.add(match.index + match[0].length)
119
+ }
120
+ })
121
+ // Spans use slice-compatible offsets: start is inclusive, end is exclusive.
122
+ const groups = []
123
+ const prefix = syllabicPrefix.exec(word)
124
+ if (prefix) {
125
+ groups.push({ start: 0, end: prefix[0].length })
126
+ }
127
+ // Mark the captured consonants without inserting letters or shifting offsets.
128
+ const voiced = new Set()
129
+ syllabicConsonants.forEach(pattern => {
130
+ const match = pattern.exec(word)
131
+ if (match) {
132
+ voiced.add(match.index + match[0].lastIndexOf(match[1]))
133
+ }
134
+ })
135
+ for (let i = 0; i < word.length; i += 1) {
136
+ if ((!isVowel.test(word[i]) && !voiced.has(i)) || silent.has(i)) {
137
+ continue
138
+ }
139
+ const previous = groups[groups.length - 1]
140
+ // Only adjacent pronounced vowels may extend an existing span.
141
+ const blended =
142
+ previous &&
143
+ previous.end === i &&
144
+ !breaks.has(i) &&
145
+ joined.some(rule => rule.match.test(word.slice(Math.max(0, i - rule.before), i + rule.after)))
146
+ if (blended) {
147
+ previous.end = i + 1
148
+ } else {
149
+ groups.push({ start: i, end: i + 1 })
150
+ }
151
+ }
152
+ return groups
153
+ }
154
+
155
+ export default vowelSpan
@@ -0,0 +1,86 @@
1
+ import compoundPrefixes from './_compound-prefixes.js'
2
+
3
+ // Turn vowel spans into cut positions by assigning the consonants between them.
4
+ // Consonant clusters allowed to start a chunk; the picker checks longest first.
5
+ const onset = new Set([
6
+ 'bl', 'br', 'ch', 'cl', 'cr', 'dr', 'fl', 'fr', 'gl', 'gr',
7
+ 'ph', 'pl', 'pr', 'qu', 'sh', 'sk', 'sl', 'sm', 'sn', 'sp',
8
+ 'st', 'sw', 'th', 'tr', 'tw', 'wh', 'scr', 'shr', 'spl', 'spr', 'str', 'thr',
9
+ ])
10
+
11
+ // Applied in order after onset placement; later rules take precedence.
12
+ // left/right are offsets from the surrounding vowel groups' edges.
13
+ // minLeft only moves a boundary forward, preserving a longer consonant cluster.
14
+ // gap matches intervening letters; tail matches the complete remaining spelling.
15
+ const adjustments = [
16
+ { minGap: 1, maxGap: 1, gap: /^x$/, left: 1 }, // box-es
17
+ { minGap: 2, gap: /^(.)\1/, minLeft: 1 }, // rab-bit
18
+ { minGap: 2, tail: /^ed$/, right: -1 }, // res-ted
19
+ { minGap: 2, gap: /^sh$/, tail: /^(es|ing)$/, right: 0 }, // wish-ing
20
+ { minGap: 2, gap: /l$/, tail: /^e(?:[sd]|['’](?:s|d|ll|re|ve))?$/, right: -2 }, // bot-tle, ta-bles, bub-bled
21
+ { minGap: 2, gap: /l$/, tail: /^e(?:ments?|ships?|ness(?:es)?|less|ful(?:ly)?|ly)(?:['’]s)?$/, right: -2 }, // set-tle-ment, bat-tle-ships
22
+ { minGap: 1, gap: /l$/, tail: /^ess(?:ly|ness(?:es)?)?$/, right: -1 }, // help-less: keep the suffix intact
23
+ { minGap: 3, gap: /^ssl$/, tail: /^y$/, right: -1 }, // help-less-ly, not help-les-sly
24
+ { minGap: 1, gap: /^(?:s|th)$/, tail: /^m(?:s|['’]s)?$/, right: 0 }, // chas-m, rhyth-m
25
+ { minGap: 1, tail: /^n['’]t$/, right: 0 }, // could-n't: keep the base consonants together
26
+ { minGap: 1, gap: /^[-‐‑–—]+$/, right: 0 }, // re-enter: keep the hyphen on the left
27
+ ]
28
+
29
+ // Choose one cut between each pair of vowel spans, preserving their count.
30
+ const pickBoundaries = (word, vowelSpans) => {
31
+ const cuts = []
32
+ // Keep recognized compound components intact, such as side-walk and some-one.
33
+ const compoundCuts = compoundPrefixes.flatMap(pattern => {
34
+ const match = pattern.exec(word)
35
+ return match ? [match[0].length] : []
36
+ })
37
+ for (let i = 1; i < vowelSpans.length; i += 1) {
38
+ // The gap contains the letters not assigned to either vowel span.
39
+ const left = vowelSpans[i - 1].end
40
+ const right = vowelSpans[i].start
41
+ const gap = word.slice(left, right)
42
+ // Match full suffixes so long endings are not mistaken for shorter ones.
43
+ const tail = word.slice(right)
44
+ // Adjacent vowels split directly; a single consonant starts the next chunk.
45
+ let cut = left
46
+ if (gap.length > 1) {
47
+ cut = right - 1
48
+ // Prefer the longest recognized consonant cluster at the next chunk's start.
49
+ for (let size = Math.min(3, gap.length); size > 1; size -= 1) {
50
+ if (onset.has(gap.slice(-size))) {
51
+ cut = right - size
52
+ break
53
+ }
54
+ }
55
+ }
56
+ // Apply every matching adjustment in order, allowing specific rules to override.
57
+ adjustments.forEach(rule => {
58
+ if (
59
+ gap.length < rule.minGap ||
60
+ (rule.maxGap !== undefined && gap.length > rule.maxGap) ||
61
+ (rule.gap && !rule.gap.test(gap)) ||
62
+ (rule.tail && !rule.tail.test(tail))
63
+ ) {
64
+ return
65
+ }
66
+ // Exact offsets replace the cut; minLeft only advances an earlier cut.
67
+ if (rule.left !== undefined) {
68
+ cut = left + rule.left
69
+ } else if (rule.right !== undefined) {
70
+ cut = right + rule.right
71
+ } else {
72
+ cut = Math.max(cut, left + rule.minLeft)
73
+ }
74
+ })
75
+ // A compound boundary overrides ordinary consonant placement within this gap.
76
+ compoundCuts.forEach(boundary => {
77
+ if (boundary >= left && boundary <= right) {
78
+ cut = boundary
79
+ }
80
+ })
81
+ cuts.push(cut)
82
+ }
83
+ return cuts
84
+ }
85
+
86
+ export default pickBoundaries