VBA JavaScript object doesn't support this property or method
Asked Answered
L

1

1

Im trying to get the Text Statistics function from https://github.com/cgiffard/TextStatistics.js/blob/master/index.js

Working in Excel

I have minified the JavaScript code to shorten the concatenations

Function Text_Statistics1(textString As String)

Dim code As String

code = "function text_stats(teststringtoprocess){(function(e){function t(e){var t=['li','p','h1','h2','h3','h4','h5','h6','dd'];t.forEach(function(t){e=e.replace('</'+t+'>','.')});e=e.replace(/<[^>]+>/g,'').replace(/[,:;()\-]/,' ').replace(/[\.!?]/,'.').replace(/^\s+/,'').replace(/[ ]*(\n|\r\n|\r)[ ]*/,' ').replace(/([\.])[\. ]+/,'.').replace(/[ ]*([\.])/,'. ').replace(/\s+/,' ').replace(/\s+$/,'');e+='.';return e}function r(e){return new n(e)}var n=function(n){this.text=n?t(n):this.text};n.prototype.fleschKincaidReadingEase=function(e){e=e?t(e):this.text;return Math.round((206.835-1.015*this.averageWordsPerSentence(e)-84.6*this.averageSyllablesPerWord(e))*10)/10};n.prototype.fleschKincaidGradeLevel=function(e){e=e?t(e):this.text;return Math.round((.39*this.averageWordsPerSentence(e)+11.8*this.averageSyllablesPerWord(e)-15.59)*10)/10};n.prototype.gunningFogScore=function(e){e=e?t(e):this.text;"
code = code + "return Math.round((this.averageWordsPerSentence(e)+this.percentageWordsWithThreeSyllables(e,false))*.4*10)/10};n.prototype.colemanLiauIndex=function(e){e=e?t(e):this.text;return Math.round((5.89*(this.letterCount(e)/this.wordCount(e))-.3*(this.sentenceCount(e)/this.wordCount(e))-15.8)*10)/10};n.prototype.smogIndex=function(e){e=e?t(e):this.text;return Math.round(1.043*Math.sqrt(this.wordsWithThreeSyllables(e)*(30/this.sentenceCount(e))+3.1291)*10)/10};n.prototype.automatedReadabilityIndex=function(e){e=e?t(e):this.text;"
code = code + "return Math.round((4.71*(this.letterCount(e)/this.wordCount(e))+.5*(this.wordCount(e)/this.sentenceCount(e))-21.43)*10)/10};n.prototype.textLength=function(e){e=e?t(e):this.text;return e.length};n.prototype.letterCount=function(e){e=e?t(e):this.text;e=e.replace(/[^a-z]+/ig,'');return e.length};n.prototype.sentenceCount=function(e){e=e?t(e):this.text;return e.replace(/[^\.!?]/g,'').length||1};n.prototype.wordCount=function(e){e=e?t(e):this.text;return e.split(/[^a-z0-9]+/i).length||1};n.prototype.averageWordsPerSentence=function(e){e=e?t(e):this.text;"
code = code + "return this.wordCount(e)/this.sentenceCount(e)};n.prototype.averageSyllablesPerWord=function(e){e=e?t(e):this.text;var n=0,r=this.wordCount(e),i=this;e.split(/\s+/).forEach(function(e){n+=i.syllableCount(e)});return(n||1)/(r||1)};n.prototype.wordsWithThreeSyllables=function(e,n){e=e?t(e):this.text;var r=0,i=this;n=n===false?false:true;e.split(/\s+/).forEach(function(e){if(!e.match(/^[A-Z]/)||n){if(i.syllableCount(e)>2)r++}});return r};n.prototype.percentageWordsWithThreeSyllables=function(e,n){e=e?t(e):this.text;return this.wordsWithThreeSyllables(e,n)/this.wordCount(e)*100};n.prototype.syllableCount=function(e){var t=0,n=0,r=0;e=e.toLowerCase().replace(/[^a-z]/g,'');var i={simile:3,forever:3,shoreline:2};if(i.hasOwnProperty(e))return i[e];var s=[/cial/,/tia/,/cius/,/cious/,/giu/,/ion/,/iou/,/sia$/,/[^aeiuoyt]{2,}ed$/,/.ely$/,/[cg]h?e[rsd]?$/,/rved?$/,/[aeiouy][dt]es?$/,/[aeiouy][^aeiouydt]e[rsd]?$/,/^[dr]e[aeiou][^aeiou]+$/,/[aeiouy]rse$/];"
code = code + "var o=[/ia/,/riet/,/dien/,/iu/,/io/,/ii/,/[aeiouym]bl$/,/[aeiou]{3}/,/^mc/,/ism$/,/([^aeiouy])\1l$/,/[^l]lien/,/^coa[dglx]./,/[^gq]ua[^auieo]/,/dnt$/,/uity$/,/ie(r|st)$/];var u=[/^un/,/^fore/,/ly$/,/less$/,/ful$/,/ers?$/,/ings?$/];u.forEach(function(t){if(e.match(t)){e=e.replace(t,'');n++}});r=e.split(/[^aeiouy]+/ig).filter(function(e){return!!e.replace(/\s+/ig,'').length}).length;t=r+n;s.forEach(function(n){if(e.match(n))t--});o.forEach(function(n){if(e.match(n))t++});return t||1};typeof module!='undefined'&&module.exports?module.exports=r:typeof define!='undefined'?define('textstatistics',[],function(){return r}):e.textstatistics=r})(this);"

'code = code + " return textstatistics(s).fleschKincaidReadingEase();" & _
'"return stat.fleschKincaidReadingEase();" & _

code = code + "return textstatistics(teststringtoprocess).fleschKincaidReadingEase();}"
'code = code + "return textstatistics(teststringtoprocess);}"



Dim o As New ScriptControl
    o.Language = "JScript"
    With o
        .AddCode code
        Text_Statistics1 = .Run("text_stats", textString)
    End With

End Function

I'm getting object doesn't support this property or method - I think its due to the instantiation of Text Statistics.

Do I need to convert the javascript to just be a set of functions?

UPDATE: Slightly different approach using eval

Function Text_Stat(textString As String, textstat As String)

Dim code As String

code = "(function(e){function t(e){var t=['li','p','h1','h2','h3','h4','h5','h6','dd'];t.forEach(function(t){e=e.replace('</'+t+'>','.')});e=e.replace(/<[^>]+>/g,'').replace(/[,:;()\-]/,' ').replace(/[\.!?]/,'.').replace(/^\s+/,'').replace(/[ ]*(\n|\r\n|\r)[ ]*/,' ').replace(/([\.])[\. ]+/,'.').replace(/[ ]*([\.])/,'. ').replace(/\s+/,' ').replace(/\s+$/,'');e+='.';return e}function r(e){return new n(e)}var n=function(n){this.text=n?t(n):this.text};n.prototype.fleschKincaidReadingEase=function(e){e=e?t(e):this.text;return Math.round((206.835-1.015*this.averageWordsPerSentence(e)-84.6*this.averageSyllablesPerWord(e))*10)/10};n.prototype.fleschKincaidGradeLevel=function(e){e=e?t(e):this.text;" & _
"return Math.round((.39*this.averageWordsPerSentence(e)+11.8*this.averageSyllablesPerWord(e)-15.59)*10)/10};n.prototype.gunningFogScore=function(e){e=e?t(e):this.text;return Math.round((this.averageWordsPerSentence(e)+this.percentageWordsWithThreeSyllables(e,false))*.4*10)/10};n.prototype.colemanLiauIndex=function(e){e=e?t(e):this.text;return Math.round((5.89*(this.letterCount(e)/this.wordCount(e))-.3*(this.sentenceCount(e)/this.wordCount(e))-15.8)*10)/10};n.prototype.smogIndex=function(e){e=e?t(e):this.text;return Math.round(1.043*Math.sqrt(this.wordsWithThreeSyllables(e)*(30/this.sentenceCount(e))+3.1291)*10)/10};n.prototype.automatedReadabilityIndex=function(e){e=e?t(e):this.text;" & _
"return Math.round((4.71*(this.letterCount(e)/this.wordCount(e))+.5*(this.wordCount(e)/this.sentenceCount(e))-21.43)*10)/10};n.prototype.textLength=function(e){e=e?t(e):this.text;return e.length};n.prototype.letterCount=function(e){e=e?t(e):this.text;e=e.replace(/[^a-z]+/ig,'');return e.length};n.prototype.sentenceCount=function(e){e=e?t(e):this.text;return e.replace(/[^\.!?]/g,'').length||1};n.prototype.wordCount=function(e){e=e?t(e):this.text;return e.split(/[^a-z0-9]+/i).length||1};n.prototype.averageWordsPerSentence=function(e){e=e?t(e):this.text;return this.wordCount(e)/this.sentenceCount(e)};n.prototype.averageSyllablesPerWord=function(e){e=e?t(e):this.text;var n=0,r=this.wordCount(e),i=this;e.split(/\s+/).forEach(function(e){n+=i.syllableCount(e)});return(n||1)/(r||1)};n.prototype.wordsWithThreeSyllables=function(e,n){e=e?t(e):this.text;" & _
"var r=0,i=this;n=n===false?false:true;e.split(/\s+/).forEach(function(e){if(!e.match(/^[A-Z]/)||n){if(i.syllableCount(e)>2)r++}});return r};n.prototype.percentageWordsWithThreeSyllables=function(e,n){e=e?t(e):this.text;" & _
"return this.wordsWithThreeSyllables(e,n)/this.wordCount(e)*100};n.prototype.syllableCount=function(e){var t=0,n=0,r=0;e=e.toLowerCase().replace(/[^a-z]/g,'');var i={simile:3,forever:3,shoreline:2};if(i.hasOwnProperty(e))return i[e];var s=[/cial/,/tia/,/cius/,/cious/,/giu/,/ion/,/iou/,/sia$/,/[^aeiuoyt]{2,}ed$/,/.ely$/,/[cg]h?e[rsd]?$/,/rved?$/,/[aeiouy][dt]es?$/,/[aeiouy][^aeiouydt]e[rsd]?$/,/^[dr]e[aeiou][^aeiou]+$/,/[aeiouy]rse$/];var o=[/ia/,/riet/,/dien/,/iu/,/io/,/ii/,/[aeiouym]bl$/,/[aeiou]{3}/,/^mc/,/ism$/,/([^aeiouy])\1l$/,/[^l]lien/,/^coa[dglx]./,/[^gq]ua[^auieo]/,/dnt$/,/uity$/,/ie(r|st)$/];" & _
"var u=[/^un/,/^fore/,/ly$/,/less$/,/ful$/,/ers?$/,/ings?$/];u.forEach(function(t){if(e.match(t)){e=e.replace(t,'');n++}});r=e.split(/[^aeiouy]+/ig).filter(function(e){return!!e.replace(/\s+/ig,'').length}).length;t=r+n;s.forEach(function(n){if(e.match(n))t--});o.forEach(function(n){if(e.match(n))t++});return t||1};typeof module!='undefined'&&module.exports?module.exports=r:typeof define!='undefined'?define('textstatistics',[],function(){return r}):e.textstatistics=r})(this);" & _
"var stat = new textstatistics('Your text here');alert(stat.sentenceCount('This. dfgdfg. is. a. long. sentence.'));"


Dim o As New ScriptControl
    o.Language = "JScript"
    With o
        .AllowUI = True
        .AddCode code
        .Eval "stat.sentenceCount('This. dfgdfg. is. a. long. sentence.')"
        'result = .Eval(code)
        'Debug.Print .Eval("'Hello World'.substring(1, 4);")
        'result = .Eval(result)
        'Text_Stat = .Run(result)
    End With

End Function

JSFiddle shows it working here http://jsfiddle.net/hwr26dkf/

UPDATE: 01/10/2014 Final Working VBA version thanks to Michael Petch

Function Text_Statistics(statType As Integer, textString As String)

Dim wc, sc As Integer
Dim s1, s2, code As String
Dim oTextStats As Object
Dim o As New ScriptControl

code = "function cleanText(e){var t=['li','p','h1','h2','h3','h4','h5','h6','dd'];t.forEach(function(t){e=e.replace('</'+t+'>','.')});e=e.replace(/<[^>]+>/g,'').replace(/[,:;()\-]/,' ').replace(/[\.!?]/,'.').replace(/^\s+/,'').replace(/[ ]*(\n|\r\n|\r)[ ]*/,' ').replace(/([\.])[\. ]+/,'.').replace(/[ ]*([\.])/,'. ').replace(/\s+/,' ').replace(/\s+$/,'');e+='.';return e}function textStatistics(e){return new TextStatistics(e)}if(!Array.prototype.forEach){Array.prototype.forEach=function(e){var t=this.length;" & _
"if(typeof e!='function')throw new TypeError;var n=arguments[1];for(var r=0;r<t;r++){if(r in this)e.call(n,this[r],r,this)}}}if(!Array.prototype.filter){Array.prototype.filter=function(e){'use strict';if(this===void 0||this===null){throw new TypeError}var t=Object(this);var n=t.length>>>0;if(typeof e!=='function'){throw new TypeError}var r=[];var i=arguments.length>=2?arguments[1]:void 0;for(var s=0;s<n;s++){if(s in t){var o=t[s];if(e.call(i,o,s,t)){r.push(o)}}}return r}}var TextStatistics=function(t){this.text=t?cleanText(t):this.text};TextStatistics.prototype.fleschKincaidReadingEase=function(e){e=e?cleanText(e):this.text;return Math.round((206.835-1.015*this.averageWordsPerSentence(e)-84.6*this.averageSyllablesPerWord(e))*10)/10};TextStatistics.prototype.fleschKincaidGradeLevel=function(e){e=e?cleanText(e):this.text;" & _
"return Math.round((.39*this.averageWordsPerSentence(e)+11.8*this.averageSyllablesPerWord(e)-15.59)*10)/10};TextStatistics.prototype.gunningFogScore=function(e){e=e?cleanText(e):this.text;return Math.round((this.averageWordsPerSentence(e)+this.percentageWordsWithThreeSyllables(e,false))*.4*10)/10};TextStatistics.prototype.colemanLiauIndex=function(e){e=e?cleanText(e):this.text;return Math.round((5.89*(this.letterCount(e)/this.wordCount(e))-.3*(this.sentenceCount(e)/this.wordCount(e))-15.8)*10)/10};" & _
"TextStatistics.prototype.smogIndex=function(e){e=e?cleanText(e):this.text;return Math.round(1.043*Math.sqrt(this.wordsWithThreeSyllables(e)*(30/this.sentenceCount(e))+3.1291)*10)/10};TextStatistics.prototype.automatedReadabilityIndex=function(e){e=e?cleanText(e):this.text;return Math.round((4.71*(this.letterCount(e)/this.wordCount(e))+.5*(this.wordCount(e)/this.sentenceCount(e))-21.43)*10)/10};TextStatistics.prototype.textLength=function(e){e=e?cleanText(e):this.text;return e.length};TextStatistics.prototype.letterCount=function(e){e=e?cleanText(e):this.text;e=e.replace(/[^a-z]+/ig,'');return e.length};TextStatistics.prototype.sentenceCount=function(e){e=e?cleanText(e):this.text;" & _
"return e.replace(/[^\.!?]/g,'').length||1};TextStatistics.prototype.wordCount=function(e){e=e?cleanText(e):this.text;return e.split(/[^a-z0-9]+/i).length||1};TextStatistics.prototype.averageWordsPerSentence=function(e){e=e?cleanText(e):this.text;return this.wordCount(e)/this.sentenceCount(e)};TextStatistics.prototype.averageSyllablesPerWord=function(e){e=e?cleanText(e):this.text;" & _
"var t=0,n=this.wordCount(e),r=this;e.split(/\s+/).forEach(function(e){t+=r.syllableCount(e)});return(t||1)/(n||1)};TextStatistics.prototype.wordsWithThreeSyllables=function(e,t){e=e?cleanText(e):this.text;var n=0,r=this;t=t===false?false:true;e.split(/\s+/).forEach(function(e){if(!e.match(/^[A-Z]/)||t){if(r.syllableCount(e)>2)n++}});return n};TextStatistics.prototype.percentageWordsWithThreeSyllables=function(e,t){e=e?cleanText(e):this.text;return this.wordsWithThreeSyllables(e,t)/this.wordCount(e)*100};" & _
"TextStatistics.prototype.syllableCount=function(e){var t=0,n=0,r=0;e=e.toLowerCase().replace(/[^a-z]/g,'');var i={simile:3,forever:3,shoreline:2};if(i.hasOwnProperty(e))return i[e];var s=[/cial/,/tia/,/cius/,/cious/,/giu/,/ion/,/iou/,/sia$/,/[^aeiuoyt]{2,}ed$/,/.ely$/,/[cg]h?e[rsd]?$/,/rved?$/,/[aeiouy][dt]es?$/,/[aeiouy][^aeiouydt]e[rsd]?$/,/^[dr]e[aeiou][^aeiou]+$/,/[aeiouy]rse$/];var o=[/ia/,/riet/,/dien/,/iu/,/io/,/ii/,/[aeiouym]bl$/,/[aeiou]{3}/,/^mc/,/ism$/,/([^aeiouy])\1l$/,/[^l]lien/,/^coa[dglx]./,/[^gq]ua[^auieo]/,/dnt$/,/uity$/,/ie(r|st)$/];" & _
"var u=[/^un/,/^fore/,/ly$/,/less$/,/ful$/,/ers?$/,/ings?$/];u.forEach(function(t){if(e.match(t)){e=e.replace(t,'');n++}});r=e.split(/[^aeiouy]+/ig).filter(function(e){return!!e.replace(/\s+/ig,'').length}).length;t=r+n;s.forEach(function(n){if(e.match(n))t--});o.forEach(function(n){if(e.match(n))t++});return t||1}"

With o
    .Language = "JScript"
    .AddCode code
    ' Create a TextStatistics object initially with no text.
    ' textStatistics is a function that creates TextStatistics objects
    Set oTextStats = .Eval("textStatistics()")

    ' Now simply call TextStatistics methods directly
    wc = oTextStats.averageWordsPerSentence(textString)
    sc = oTextStats.syllableCount(textString)

    ' Alternatively you can create a TextStatistics object with the text
    ' and call the methods with a blank string to return the values
    ' for the string passed in the constructor

    'Set oTextStats = .Eval("textStatistics('" + textString + "')")
    'wc = oTextStats.wordCount("")
    'sc = oTextStats.sentenceCount("")


    Select Case statType
    Case 1
        Text_Statistics = oTextStats.wordCount(textString)
    Case 2
        Text_Statistics = oTextStats.sentenceCount(textString)
    Case 3
        Text_Statistics = oTextStats.fleschKincaidReadingEase(textString)
    Case 4
        Text_Statistics = oTextStats.fleschKincaidGradeLevel(textString)
    Case 5
        Text_Statistics = oTextStats.gunningFogScore(textString)
    Case 6
        Text_Statistics = oTextStats.colemanLiauIndex(textString)
    Case 7
        Text_Statistics = oTextStats.smogIndex(textString)
    Case 8
        Text_Statistics = oTextStats.automatedReadabilityIndex(textString)
    Case 9
        Text_Statistics = oTextStats.textLength(textString)
   Case 10
        Text_Statistics = oTextStats.letterCount(textString)
   Case 11
        Text_Statistics = oTextStats.averageWordsPerSentence(textString)
   Case 12
        Text_Statistics = oTextStats.averageSyllablesPerWord(textString)

End Select

End With
End Function
Labile answered 25/9, 2014 at 11:24 Comment(4)
I tried that but I get wrong number of argiments - but that let me down a different line of investigation - why not inject the vba vars into the JavaScript function - however this gave me the same error which was JavaScript object doesn't support this property or methodLabile
im using 32bit Excel 64 bit Win 7Labile
are you able to compile the code with Option Explicit at the top of the module? If not, check your Object Library References.Dabster
Thanks for the suggestion it did find one issue which was the function name - but the overall problem still remainsLabile
F
2

I spent some time this afternoon learning Javascript and then trying to figure out what is going on with your TextStatistics class when run in Microsoft's ScriptControl object. Rather than start with the mangled code in VBA I went back to the code in github that the OP referenced. The first thing I discovered is that ScriptControl will parse and execute the anonymous global function however once that code is added by ScriptControl it seems to lose track of the TextStatistics object. So the first thing I did was simply remove the anonymous global function by removing this at the top:

(function(glob) {

and removing these line at the bottom:

(typeof module != "undefined" && module.exports) ? (module.exports = textStatistics) : (typeof define != "undefined" ? (define("textstatistics", [], function() { return textStatistics; })) : (glob.textstatistics = textStatistics));
})(this);

Once I removed that as a source of problems I discovered that I could create new instances of TextStatistics but I could not assign text to them properly. Neither as a parameter using new or through calling a method like sentenceCount(). This had me perplexed. Since creating an instance of TextStatistics couldn't be done properly I decided to review the constructor. It is simple but it called cleanText. One thing that stood out at me was the forEach. On a hunch I did some digging about Javascript / ScriptControl and then forEach. I learned that ScriptControl uses ECMAScript and not Javascript. Once I had that straight I found a link with this information which includes this comment: about the forEach method:

This method is a JavaScript extension to the ECMA-262 standard; as such it may not be present in other implementations of the standard. To make it work you need to add following code at the top of your script:

And this code:

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function (fun /*, thisp*/ ) {
        var len = this.length;
        if (typeof fun != 'function') throw new TypeError();

        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in this) fun.call(thisp, this[i], i, this);
        }
    };
}

After providing my original answer, the OP discovered that functions involving syllables didn't work. There is another function that showed up in a later ECMA specification that ScriptControl didn't support. That was the polyfill filter function on arrays. According to this Mozilla documentation:

filter was added to the ECMA-262 standard in the 5th edition; as such it may not be present in all implementations of the standard. You can work around this by inserting the following code at the beginning of your scripts, allowing use of filter in ECMA-262 implementations which do not natively support it.

The code provided that meets the specification:

if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun/*, thisArg*/) {
    'use strict';

    if (this === void 0 || this === null) {
      throw new TypeError();
    }

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      if (i in t) {
        var val = t[i];

        // NOTE: Technically this should Object.defineProperty at
        //       the next index, as push can be affected by
        //       properties on Object.prototype and Array.prototype.
        //       But that method's new, and collisions should be
        //       rare, so use the more-compatible alternative.
        if (fun.call(thisArg, val, i, t)) {
          res.push(val);
        }
      }
    }

    return res;
  };
}

Was it so simple? Was this the cause of those problems? Yes it was. I added that code to the top of the script and VBA and ScriptControl were content. So before minifying and converting all " to ' the Javascript code in its entirety looks like this:

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function (fun /*, thisp*/ ) {
        var len = this.length;
        if (typeof fun != 'function') throw new TypeError();

        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in this) fun.call(thisp, this[i], i, this);
        }
    };
}

if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun/*, thisArg*/) {
    'use strict';

    if (this === void 0 || this === null) {
      throw new TypeError();
    }

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      if (i in t) {
        var val = t[i];

        // NOTE: Technically this should Object.defineProperty at
        //       the next index, as push can be affected by
        //       properties on Object.prototype and Array.prototype.
        //       But that method's new, and collisions should be
        //       rare, so use the more-compatible alternative.
        if (fun.call(thisArg, val, i, t)) {
          res.push(val);
        }
      }
    }

    return res;
  };
}

function cleanText(text) {
    // all these tags should be preceeded by a full stop. 
    var fullStopTags = ['li', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dd'];

    fullStopTags.forEach(function (tag) {
        text = text.replace('</' + tag + '>', '.');
    });

    text = text.replace(/<[^>]+>/g, '') // Strip tags
    .replace(/[,:;()\-]/, ' ') // Replace commans, hyphens etc (count them as spaces)
    .replace(/[\.!?]/, '.') // Unify terminators
    .replace(/^\s+/, '') // Strip leading whitespace
    .replace(/[ ]*(\n|\r\n|\r)[ ]*/, ' ') // Replace new lines with spaces
    .replace(/([\.])[\. ]+/, '.') // Check for duplicated terminators
    .replace(/[ ]*([\.])/, '. ') // Pad sentence terminators
    .replace(/\s+/, ' ') // Remove multiple spaces
    .replace(/\s+$/, ''); // Strip trailing whitespace

    text += '.'; // Add final terminator, just in case it's missing.

    return text;
}

var TextStatistics = function TextStatistics(text) {
    this.text = text ? cleanText(text) : this.text;
};

TextStatistics.prototype.fleschKincaidReadingEase = function (text) {
    text = text ? cleanText(text) : this.text;
    return Math.round((206.835 - (1.015 * this.averageWordsPerSentence(text)) - (84.6 * this.averageSyllablesPerWord(text))) * 10) / 10;
};

TextStatistics.prototype.fleschKincaidGradeLevel = function (text) {
    text = text ? cleanText(text) : this.text;
    return Math.round(((0.39 * this.averageWordsPerSentence(text)) + (11.8 * this.averageSyllablesPerWord(text)) - 15.59) * 10) / 10;
};

TextStatistics.prototype.gunningFogScore = function (text) {
    text = text ? cleanText(text) : this.text;
    return Math.round(((this.averageWordsPerSentence(text) + this.percentageWordsWithThreeSyllables(text, false)) * 0.4) * 10) / 10;
};

TextStatistics.prototype.colemanLiauIndex = function (text) {
    text = text ? cleanText(text) : this.text;
    return Math.round(((5.89 * (this.letterCount(text) / this.wordCount(text))) - (0.3 * (this.sentenceCount(text) / this.wordCount(text))) - 15.8) * 10) / 10;
};

TextStatistics.prototype.smogIndex = function (text) {
    text = text ? cleanText(text) : this.text;
    return Math.round(1.043 * Math.sqrt((this.wordsWithThreeSyllables(text) * (30 / this.sentenceCount(text))) + 3.1291) * 10) / 10;
};

TextStatistics.prototype.automatedReadabilityIndex = function (text) {
    text = text ? cleanText(text) : this.text;
    return Math.round(((4.71 * (this.letterCount(text) / this.wordCount(text))) + (0.5 * (this.wordCount(text) / this.sentenceCount(text))) - 21.43) * 10) / 10;
};

TextStatistics.prototype.textLength = function (text) {
    text = text ? cleanText(text) : this.text;
    return text.length;
};

TextStatistics.prototype.letterCount = function (text) {
    text = text ? cleanText(text) : this.text;
    text = text.replace(/[^a-z]+/ig, '');
    return text.length;
};

TextStatistics.prototype.sentenceCount = function (text) {
    text = text ? cleanText(text) : this.text;

    // Will be tripped up by 'Mr.' or 'U.K.'. Not a major concern at this point.
    return text.replace(/[^\.!?]/g, '').length || 1;
};

TextStatistics.prototype.wordCount = function (text) {
    text = text ? cleanText(text) : this.text;
    return text.split(/[^a-z0-9]+/i).length || 1;
};

TextStatistics.prototype.averageWordsPerSentence = function (text) {
    text = text ? cleanText(text) : this.text;
    return this.wordCount(text) / this.sentenceCount(text);
};

TextStatistics.prototype.averageSyllablesPerWord = function (text) {
    text = text ? cleanText(text) : this.text;
    var syllableCount = 0,
        wordCount = this.wordCount(text),
        self = this;

    text.split(/\s+/).forEach(function (word) {
        syllableCount += self.syllableCount(word);
    });

    // Prevent NaN...
    return (syllableCount || 1) / (wordCount || 1);
};

TextStatistics.prototype.wordsWithThreeSyllables = function (text, countProperNouns) {
    text = text ? cleanText(text) : this.text;
    var longWordCount = 0,
        self = this;

    countProperNouns = countProperNouns === false ? false : true;

    text.split(/\s+/).forEach(function (word) {

        // We don't count proper nouns or capitalised words if the countProperNouns attribute is set.
        // Defaults to true.
        if (!word.match(/^[A-Z]/) || countProperNouns) {
            if (self.syllableCount(word) > 2) longWordCount++;
        }
    });

    return longWordCount;
};

TextStatistics.prototype.percentageWordsWithThreeSyllables = function (text, countProperNouns) {
    text = text ? cleanText(text) : this.text;

    return (this.wordsWithThreeSyllables(text, countProperNouns) / this.wordCount(text)) * 100;
};

TextStatistics.prototype.syllableCount = function (word) {
    var syllableCount = 0,
        prefixSuffixCount = 0,
        wordPartCount = 0;

    // Prepare word - make lower case and remove non-word characters
    word = word.toLowerCase().replace(/[^a-z]/g, '');

    // Specific common exceptions that don't follow the rule set below are handled individually
    // Array of problem words (with word as key, syllable count as value)
    var problemWords = {
        'simile': 3,
            'forever': 3,
            'shoreline': 2
    };

    // Return if we've hit one of those...
    if (problemWords.hasOwnProperty(word)) return problemWords[word];

    // These syllables would be counted as two but should be one
    var subSyllables = [
        /cial/,
        /tia/,
        /cius/,
        /cious/,
        /giu/,
        /ion/,
        /iou/,
        /sia$/,
        /[^aeiuoyt]{2,}ed$/,
        /.ely$/,
        /[cg]h?e[rsd]?$/,
        /rved?$/,
        /[aeiouy][dt]es?$/,
        /[aeiouy][^aeiouydt]e[rsd]?$/,
        /^[dr]e[aeiou][^aeiou]+$/, // Sorts out deal, deign etc
    /[aeiouy]rse$/ // Purse, hearse
    ];

    // These syllables would be counted as one but should be two
    var addSyllables = [
        /ia/,
        /riet/,
        /dien/,
        /iu/,
        /io/,
        /ii/,
        /[aeiouym]bl$/,
        /[aeiou]{3}/,
        /^mc/,
        /ism$/,
        /([^aeiouy])\1l$/,
        /[^l]lien/,
        /^coa[dglx]./,
        /[^gq]ua[^auieo]/,
        /dnt$/,
        /uity$/,
        /ie(r|st)$/];

    // Single syllable prefixes and suffixes
    var prefixSuffix = [
        /^un/,
        /^fore/,
        /ly$/,
        /less$/,
        /ful$/,
        /ers?$/,
        /ings?$/];

    // Remove prefixes and suffixes and count how many were taken
    prefixSuffix.forEach(function (regex) {
        if (word.match(regex)) {
            word = word.replace(regex, '');
            prefixSuffixCount++;
        }
    });

    wordPartCount = word.split(/[^aeiouy]+/ig)
        .filter(function (wordPart) {
        return !!wordPart.replace(/\s+/ig, '').length;
    })
        .length;

    // Get preliminary syllable count...
    syllableCount = wordPartCount + prefixSuffixCount;

    // Some syllables do not follow normal rules - check for them
    subSyllables.forEach(function (syllable) {
        if (word.match(syllable)) syllableCount--;
    });

    addSyllables.forEach(function (syllable) {
        if (word.match(syllable)) syllableCount++;
    });

    return syllableCount || 1;
};

function textStatistics(text) {
    return new TextStatistics(text);
}

After taking this function and adding it to the code variable (See OP's Visual Basic code) I was able to create an instance of this control and call methods on it. There are a couple of different ways to use TextStatistics in VBA:

Dim wc, sc As Integer
Dim s1, s2, code As String
Dim oTextStats As Object
Dim o As New ScriptControl

code = "function cleanText(e){var t=['li','p','h1','h2','h3','h4','h5','h6','dd'];t.forEach(function(t){e=e.replace('</'+t+'>','.')});e=e.replace(/<[^>]+>/g,'').replace(/[,:;()\-]/,' ').replace(/[\.!?]/,'.').replace(/^\s+/,'').replace(/[ ]*(\n|\r\n|\r)[ ]*/,' ').replace(/([\.])[\. ]+/,'.').replace(/[ ]*([\.])/,'. ').replace(/\s+/,' ').replace(/\s+$/,'');e+='.';return e}function textStatistics(e){return new TextStatistics(e)}if(!Array.prototype.filter){Array.prototype.filter=function(e){'use strict';if(this===void 0||this===null){throw new TypeError}var t=Object(this);" & _
       "var n=t.length>>>0;if(typeof e!=='function'){throw new TypeError}var r=[];var i=arguments.length>=2?arguments[1]:void 0;for(var s=0;s<n;s++){if(s in t){var o=t[s];if(e.call(i,o,s,t)){r.push(o)}}}return r}}if(!Array.prototype.forEach){Array.prototype.forEach=function(e){var t=this.length;if(typeof e!='function')throw new TypeError;var n=arguments[1];for(var r=0;r<t;r++){if(r in this)e.call(n,this[r],r,this)}}}var TextStatistics=function(t){this.text=t?cleanText(t):this.text};" & _
       "TextStatistics.prototype.fleschKincaidReadingEase=function(e){e=e?cleanText(e):this.text;return Math.round((206.835-1.015*this.averageWordsPerSentence(e)-84.6*this.averageSyllablesPerWord(e))*10)/10};TextStatistics.prototype.fleschKincaidGradeLevel=function(e){e=e?cleanText(e):this.text;return Math.round((.39*this.averageWordsPerSentence(e)+11.8*this.averageSyllablesPerWord(e)-15.59)*10)/10};TextStatistics.prototype.gunningFogScore=function(e){e=e?cleanText(e):this.text;" & _
       "return Math.round((this.averageWordsPerSentence(e)+this.percentageWordsWithThreeSyllables(e,false))*.4*10)/10};TextStatistics.prototype.colemanLiauIndex=function(e){e=e?cleanText(e):this.text;return Math.round((5.89*(this.letterCount(e)/this.wordCount(e))-.3*(this.sentenceCount(e)/this.wordCount(e))-15.8)*10)/10};TextStatistics.prototype.smogIndex=function(e){e=e?cleanText(e):this.text;return Math.round(1.043*Math.sqrt(this.wordsWithThreeSyllables(e)*(30/this.sentenceCount(e))+3.1291)*10)/10};" & _
       "TextStatistics.prototype.automatedReadabilityIndex=function(e){e=e?cleanText(e):this.text;return Math.round((4.71*(this.letterCount(e)/this.wordCount(e))+.5*(this.wordCount(e)/this.sentenceCount(e))-21.43)*10)/10};TextStatistics.prototype.textLength=function(e){e=e?cleanText(e):this.text;return e.length};TextStatistics.prototype.letterCount=function(e){e=e?cleanText(e):this.text;e=e.replace(/[^a-z]+/ig,'');return e.length};TextStatistics.prototype.sentenceCount=function(e){e=e?cleanText(e):this.text;" & _
       "return e.replace(/[^\.!?]/g,'').length||1};TextStatistics.prototype.wordCount=function(e){e=e?cleanText(e):this.text;return e.split(/[^a-z0-9]+/i).length||1};TextStatistics.prototype.averageWordsPerSentence=function(e){e=e?cleanText(e):this.text;return this.wordCount(e)/this.sentenceCount(e)};TextStatistics.prototype.averageSyllablesPerWord=function(e){e=e?cleanText(e):this.text;var t=0,n=this.wordCount(e),r=this;e.split(/\s+/).forEach(function(e){t+=r.syllableCount(e)});return(t||1)/(n||1)};" & _
       "TextStatistics.prototype.wordsWithThreeSyllables=function(e,t){e=e?cleanText(e):this.text;var n=0,r=this;t=t===false?false:true;e.split(/\s+/).forEach(function(e){if(!e.match(/^[A-Z]/)||t){if(r.syllableCount(e)>2)n++}});return n};TextStatistics.prototype.percentageWordsWithThreeSyllables=function(e,t){e=e?cleanText(e):this.text;return this.wordsWithThreeSyllables(e,t)/this.wordCount(e)*100};TextStatistics.prototype.syllableCount=function(e){var t=0,n=0,r=0;e=e.toLowerCase().replace(/[^a-z]/g,'');" & _
       "var i={simile:3,forever:3,shoreline:2};if(i.hasOwnProperty(e))return i[e];var s=[/cial/,/tia/,/cius/,/cious/,/giu/,/ion/,/iou/,/sia$/,/[^aeiuoyt]{2,}ed$/,/.ely$/,/[cg]h?e[rsd]?$/,/rved?$/,/[aeiouy][dt]es?$/,/[aeiouy][^aeiouydt]e[rsd]?$/,/^[dr]e[aeiou][^aeiou]+$/,/[aeiouy]rse$/];var o=[/ia/,/riet/,/dien/,/iu/,/io/,/ii/,/[aeiouym]bl$/,/[aeiou]{3}/,/^mc/,/ism$/,/([^aeiouy])\1l$/,/[^l]lien/,/^coa[dglx]./,/[^gq]ua[^auieo]/,/dnt$/,/uity$/,/ie(r|st)$/];var u=[/^un/,/^fore/,/ly$/,/less$/,/ful$/,/ers?$/,/ings?$/];" & _
       "u.forEach(function(t){if(e.match(t)){e=e.replace(t,'');n++}});r=e.split(/[^aeiouy]+/ig).filter(function(e){return!!e.replace(/\s+/ig,'').length}).length;t=r+n;s.forEach(function(n){if(e.match(n))t--});o.forEach(function(n){if(e.match(n))t++});return t||1}"


s1 = "the quick brown fox jumps over the lazy dog"
s2 = "help me! Some Short sentence fragments. Just a test"

With o
    .Language = "JScript"
    .AddCode code
    ' Create a TextStatistics object initially with no text.
    ' textStatistics is a function that creates TextStatistics objects
    Set oTextStats = .Eval("textStatistics()")

    ' Now simply call TextStatistics methods directly
    wc = oTextStats.wordCount(s1)
    sc = oTextStats.sentenceCount(s2)

    ' Alternatively you can create a TextStatistics object with the text
    ' and call the methods with a blank string to return the values
    ' for the string passed in the constructor
    Set oTextStats = .Eval("textStatistics('" + s1 + "')")
    wc = oTextStats.wordCount("")
    sc = oTextStats.sentenceCount("")
End With
Fascist answered 30/9, 2014 at 23:43 Comment(4)
Thanks Michael - this answer is way more than I was looking for! -thanks for going the extra mile - bounty well and truly deservedLabile
OK: I spoke to soon ! :) the following work - colemanLiauIndex, automatedReadabilityIndex, textLength, sentenceCount, letterCount, wordCount, averageWordsPerSentence - The following don't smogIndex, wordsWithThreeSyllables, averageSyllablesPerWord, fleschKincaidReadingEase, fleschKincaidGradeLevel, gunningFogScore and I think it boils down to the Syllables functions... I will investigate moreLabile
Exactly - the error is the same as the OP reported. So I think your right the difference between ECMA and JavaScript - I will also investigate - my hunch is the self = this !Labile
I have found the issue with the syllableCount that may account for the issues in other functions. filter on arrays is not supported in older ECMA specifications. I have provided a code replacement and links regarding that to my answer. I haven't tested all functions but this will probably get you further. I haven't minifyed the VBA code = so you'll have to do that.Fascist

© 2022 - 2024 — McMap. All rights reserved.