Calculate LIX value of a text I've been building a simple tool to calculate the LIX value of a text (a standard measurement of the readability of a text).

My approach was to have every step of the calculation as seperate functions. In a larger and more complex tool the total words, sentences and others would be nice to have as independent functions.

Also the functions should be able to take in any string input and do their calculations. So that I would easily be able to use this in other contexts like TinyMCE editor and so forth.

So I've tried to find a way to make the code flexible. And my ambition was to write simple and pure functions.

I'm a little in doubt as to how readable and maintainable this approach is.

The click function triggering the calculation is an example. I'd really like your thoughts about this functions inside functions approach. I found it very nice to work in this way.



jQuery(document).ready(function() {


  function getText() {
    var text = jQuery('#inputLix').val();

    return text;
  }

  function countWords(input) {
    var wordCount;
    var wordsArray;
    var splitter = /\s|\n/g;
    wordsArray = input
      .replace(/(&lt;([^&gt;]+)&gt;)/ig, "")
      .replace(/,/g, "")
      .replace(/-/g, "")
      .replace(/"/g, "")
      .replace(/'/g, "")
      .split(splitter)
      .filter(Boolean);
    wordCount = wordsArray.length;
    return wordCount;
  }


  function longWordCount(input) {
    var longWordCounter = 0;
    var splitter = /\s|\n/g;
    var checkedText = input.replace(/&lt;([^&gt;]+)&gt;|[\.\+\?,\(\)"'\-\!\;\:]/ig, "");
    var wordsArray = checkedText
      .split(splitter)
      .filter(Boolean);

    for (var i in wordsArray) {
      if (wordsArray[i].charAt(0) &gt; -1) {} else if (wordsArray[i].length &gt; 6) {
        longWordCounter++;
      }
    }
    return longWordCounter;
  }


  function getSentences(input) {
    var splitter = /\?|\!|\.|\n/g;
    var checkedText = input
      .replace(/(&lt;([^&gt;]+)&gt;)/ig, "")
      .replace(/,/g, "")
      .replace(/-/g, "")
      .replace(/"/g, "")
      .replace(/'/g, "");
    var arrayOfSentences = checkedText.split(splitter);
    arrayOfSentences = arrayOfSentences.filter(Boolean);
    return arrayOfSentences;
  }

  function countSentences(input) {
    return input.length;
  }

  function calculateLix(wordCount, sentenceCount, longWordsCount) {
    var lixScore = Math.round((wordCount / sentenceCount) + ((longWordsCount * 100) / wordCount));
    return lixScore;
  }

  function outputContent(input, id) {
    jQuery(id).html(input);
  }


  jQuery("#lixButton").click(function() {
    var input = getText();
    outputContent(calculateLix(countWords(input), countSentences(getSentences(input)), longWordCount(input)), "#notification");
  });

});
.tool-wrapper {
  background: #899398;
  padding: 30px;
  border-radius: 10px;
}

textarea {
  width: 100%;
}
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;
&lt;div class="tool-wrapper"&gt;
  &lt;textarea id="inputLix" rows="15"&gt;This is just some basic text. Push the button to calculate the LIX value. And figure out how hard your text is to read.&lt;/textarea&gt;
  &lt;div class="clearfix"&gt;
    &lt;button id="lixButton" class="alignright" type="submit"&gt;Calculate Lix&lt;/button&gt;
    &lt;div id="notification" class="alignright"&gt;You haven't performed any calculations yet&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
Your code could be simplified and better organized.


You seem to be expecting HTML, as evidenced by various calls that look like .replace(/(&lt;([^&gt;]+)&gt;)/ig, "").  However, that is a half-assed way of interpreting HTML as text.

Consider that your getSentences() treats a newline as a possible sentence delimiter.  But all whitespace in HTML is considered equivalent, more or less.  If your input is indeed HTML, you should normalize all whitespace as spaces, and interpret tags such as &lt;p&gt; and &lt;br&gt; as line breaks.

Also, if the input is HTML, you ought to decode entities (&amp;65; is A, for example).
Most of your functions have parameters named input.  Well, duh.  All parameters act as input, by definition.  But what kind of input?  Here is the most baffling example, where the input is supposed to be an array of sentences:


function countSentences(input) {
  return input.length;
}



If the input is HTML text, call it html.  If it's plain text, then call it text.
The click handler is a bit convoluted.  It should look like this:

jQuery("#lixButton").click(function() {
  var html = jQuery('#inputLix').val();
  jQuery('#notification').text(lixScore(htmlToText(html)));
});


In particular, your getText() function hard-coded the ID of the textarea, which I don't recommend.  Your outputContent(), in contrast, does take a jQuery selector as a parameter (inappropriately named id), which is good, but needlessly complicated.  I would eliminate both getText() and outputContent().
The calculateLix() function is hard to use.  Why does it need so many parameters?  Why not just feed it some text and have it do the analysis?
The helper functions are inconsistently designed: countWords(input) vs. longWordCount(input) vs. countSentences(getSentences(input)).

The implementations of countWords() and longWordCount() overlap, and they treat punctuation differently (why?).  Once you have identified the words, shouldn't counting long words be a simple matter of filtering by length?

Why does getSentences() mess around with punctuation that is irrelevant to sentence-splitting?


Suggested implementation



jQuery(document).ready(function() {
  var htmlNamedEntities = {
    "amp": "&amp;",
    "quot": '"',
    "lt": "&lt;",
    "gt": "&gt;"
  };

  function htmlToText(html) {
    return html.replace(/\s+/, " ")
               .replace(/&lt;\/?\s*(p|br|hr)[^&gt;]*&gt;/ig, "\n")
               .replace(/(&lt;[^&gt;]+&gt;)|&amp;(\d+);|&amp;x([0-9a-f]+);|&amp;([a-z]+);/ig,
      function($0, tag, decimalEntity, hexEntity, namedEntity) {
        return tag ? ""
             : decimalEntity ? String.fromCharCode(parseInt(decimalEntity))
             : hexEntity ? String.fromCharCode(parseInt(hexEntity, 16))
             : htmlNamedEntities[namedEntity];
      }
    );
  }

  function words(text) {
    return text.replace(/[-']/ig, "")  // Ignore hyphens and apostrophes.
               .split(/\W+/)           // This assumes that the text is alphanumeric English.
               .filter(Boolean);
  }

  function sentences(text) {
    return text.split(/[?!.\n]+/)
               .filter(Boolean);
  }

  function lixScore(text) {
    var textWords = words(text);
    var w = textWords.length;
    var lw = textWords.filter(function(w) { return w.length &gt; 6 }).length;
    var s = sentences(text).length;
    return Math.round((w / s) + (100 * lw / w));
  }

  jQuery("#lixButton").click(function() {
    var html = jQuery('#inputLix').val();
    jQuery('#notification').text(lixScore(htmlToText(html)));
  });

});
.tool-wrapper {
  background: #899398;
  padding: 30px;
  border-radius: 10px;
}

textarea {
  width: 100%;
}
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;
&lt;div class="tool-wrapper"&gt;
  &lt;textarea id="inputLix" rows="5"&gt;This is just some basic text. Push the button to calculate the LIX value. And figure out how hard your text is to read.&lt;/textarea&gt;
  &lt;div class="clearfix"&gt;
    &lt;button id="lixButton" class="alignright" type="submit"&gt;Calculate Lix&lt;/button&gt;
    &lt;div id="notification" class="alignright"&gt;You haven't performed any calculations yet&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;