Action

Stats (selection)

Posted by agiletortoise, Last update about 5 years ago

Calculate and present preview of common readability statistics for the current text selection.

Steps

  • script

    // Make sure draft is not blank first...
    
    let selectedText = editor.getSelectedText();
    if (selectedText.length == 0) {
       alert("Empty text selection. Text required to calculate statistics.");
    	context.cancel();
    }
  • script

    // Show alert displaying common text statistics for current draft
    // Includes syllable counts, Flesh-Kincaid, SMOG index scoring, etc.
    
    // adapted from...
    // TextStatistics.js
    // Christopher Giffard (2012)
    // 1:1 API Fork of TextStatistics.php by Dave Child (Thanks mate!)
    // https://github.com/DaveChild/Text-Statistics
    
    // BEGIN LIBRARY	
    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);
    }
    // END LIBRARY
    	
    
    var result = "";
    var s = selectedText;
    var stats = textStatistics(s);
    
    var t = ["<table>"];
    function addRow(key, val, link) {
    	t.push("<tr><td class='lbl'>");
    	t.push(key);
    	t.push("</td><td class='val'>");
    	t.push(val);
    	t.push("</td>");
    	if (link) {
    		t.push("<td>");
    		t.push("<a href='" + link + "'>info</a>");
    		t.push("</td>");
    	}
    	else {
    		t.push("<td></td>");
    	}
    	t.push("</tr>")
    }
    
    addRow("Words", stats.wordCount());
    addRow("Sentences", stats.sentenceCount());
    addRow("Words per sentence", Math.round(stats.averageWordsPerSentence(), 2));
    addRow("Flesch-Kincaid Ease", stats.fleschKincaidReadingEase(), "http://www.readabilityformulas.com/flesch-reading-ease-readability-formula.php");
    addRow("Flesch-Kincaid Grade Level", stats.fleschKincaidGradeLevel(), "http://www.readabilityformulas.com/flesch-grade-level-readability-formula.php");
    addRow("Gunning Fog", stats.gunningFogScore(), "https://en.wikipedia.org/wiki/Gunning_fog_index");
    addRow("SMOG Index", stats.smogIndex(), "http://www.readabilityformulas.com/smog-readability-formula.php");
    addRow("Automated Readability", stats.automatedReadabilityIndex(), "http://www.readabilityformulas.com/automated-readability-index.php");
    addRow("Coleman Liau", stats.colemanLiauIndex(), "http://www.readabilityformulas.com/coleman-liau-readability-formula.php");
    
    t.push("</table>");
    
    draft.setTemplateTag("htmltable", t.join(""));
    
    
  • script

    // create theme sensitive css for preview
    
    if (app.themeMode == 'dark') {
    	var css = "html, body { background: #222; color: #ddd; }";
    	draft.setTemplateTag("bodystyle", css);
    }
    else {
    	var css = "html, body { background: #fff; color: #444; }";
    	draft.setTemplateTag("bodystyle", css);
    }
  • htmlpreview

    <!DOCTYPE html>
    <html dir="auto">
        <head>
            <title>Preview</title>
    		<meta name="viewport" content="width=device-width, initial-scale=1">
            <style>
            @import url(http://fonts.googleapis.com/css?family=Vollkorn:400,400italic,700,700italic&subset=latin);
    
            html, body {
                    padding:0em;
                    margin:auto;
                    max-width:55em;
                   text-align: center;
            	}
            body {
            	font: 1.3em "Vollkorn", Palatino, Times;
            	color: #333;
            	line-height: 1.4;
            	text-align: center;
            	}
            header, nav, article, footer {
            	width: 700px;
            	margin:0 auto;
            	}
            article {
            	margin-top: 4em;
            	margin-bottom: 4em;
            	min-height: 400px;
            	}
            footer {
            	margin-bottom:50px;
            	}
            video {
            	margin: 2em 0;
            	border:1px solid #ddd;
            	}
    
            nav {
            	font-size: .9em;
            	font-style: italic;
            	border-bottom: 1px solid #ddd;
            	padding: 1em 0;
            	}
            nav p {
            	margin: 0;
            	}
    
            /* Typography
            -------------------------------------------------------- */
    
            h1 {
            	margin-top: 0;
            	font-weight: normal;
            	}
            h2 {
            	font-weight: normal;
            	}
            h3 {
            	font-weight: normal;
            	font-style: italic;
            	margin-top:3em;
            	}
            p {
            	margin-top:0;
            	-webkit-hypens:auto;
            	-moz-hypens:auto;
            	hyphens:auto;
            	}
            ul {
            	list-style: square;
            	padding-left:1.2em;
            	}
            ol {
            	padding-left:1.2em;
            	}
            blockquote {
            	margin-left: 1em;
            	padding-left: 1em;
            	border-left: 1px solid #ddd;
            	}
            code {
            	font-family: "Consolas", "Menlo", "Monaco", monospace, serif;
            	font-size: .9em;
            	background: white;
            	}
            a {
            	color: #2484c1;
            	text-decoration: none;
            	}
            a:hover {
            	text-decoration: underline;
            	}
            a img {
            	border:none;
            	}
            h1 a, h1 a:hover {
            	color: #333;
            	text-decoration: none;
            	}
            hr {
            	color : #ddd;
            	height : 1px;
            	margin: 2em 0;
            	border-top : solid 1px #ddd;
            	border-bottom : none;
            	border-left: 0;
            	border-right: 0;
            	}
            p#heart{
            	font-size: 2em;
            	line-height: 1;
            	text-align: center;
            	color: #ccc;
            	}
            .red {
            	color:#B50000;
            	}
            img {
                max-width: 90%;
            }
    
    table {
      margin: 1em auto;
      border: 1px solid #aaa;
      border-collapse: collapse;
    }
    
    th {
      padding:.25em .5em;
      background: #efefef;
      border: 1px solid #ccc;  
    }
    
    td {
      padding:.25em .5em;
      text-align: left;
      border: 1px solid #ccc;
    }
    
            /* Home Page
            --------------------------- */
    
            body#index li {
            	margin-bottom: 1em;
            	}
    
    
            /* iPad
            -------------------------------------------------------- */
            @media only screen and (max-device-width: 1024px) {
            body {
            	font-size: 120%;
            	line-height: 1.4;
            	}
            } /* @iPad */
    
            /* iPhone
            -------------------------------------------------------- */
            @media only screen and (max-device-width: 480px) {
            body {
            	text-align: left;
                padding:.5em;
            	}
            article, footer {
            	width: auto;
            	}
            article {
            	padding: 0 10px;
            	}
            } /* @iPhone */
    
    .lbl {
    }
    .val {
      font-weight: bold;
      text-align: right;
    }
    
    [[bodystyle]]
    </style>
    </head>
    <body>
    <h2>Draft Statistics</h2>
        [[htmltable]]
    </body>
    </html>
    

Options

  • After Success Nothing
    Notification Error
    Log Level Error
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.