/*********************************************************************** * * Copyright (C) 2016-2020 wereturtle * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ***********************************************************************/ #include #include #include "DocumentStatistics.h" DocumentStatistics::DocumentStatistics(MarkdownDocument* document, QObject* parent) : QObject(parent), document(document) { wordCount = 0; wordCharacterCount = 0; sentenceCount = 0; paragraphCount = 0; lixLongWordCount = 0; connect(this->document, SIGNAL(contentsChange(int,int,int)), this, SLOT(onTextChanged(int,int,int))); connect(this->document, SIGNAL(textBlockRemoved(const QTextBlock&)), this, SLOT(onTextBlockRemoved(const QTextBlock&))); } DocumentStatistics::~DocumentStatistics() { } int DocumentStatistics::getWordCount() const { return wordCount; } void DocumentStatistics::onTextSelected ( const QString& selectedText, int selectionStart, int selectionEnd ) { int selectionWordCount; int selectionLixLongWordCount; int selectionWordCharacterCount; countWords ( selectedText, selectionWordCount, selectionLixLongWordCount, selectionWordCharacterCount ); int selectionSentenceCount = countSentences(selectedText); // Count the number of selected paragraphs. int selectedParagraphCount = 0; QTextBlock block = this->document->findBlock(selectionStart); QTextBlock end = this->document->findBlock(selectionEnd).next(); while (block != end) { TextBlockData* blockData = (TextBlockData*) block.userData(); if ((NULL != blockData) && !blockData->blankLine) { selectedParagraphCount++; } block = block.next(); } emit wordCountChanged(selectionWordCount); emit characterCountChanged(selectedText.length()); emit sentenceCountChanged(selectionSentenceCount); emit paragraphCountChanged(selectedParagraphCount); emit pageCountChanged(calculatePageCount(selectionWordCount)); emit complexWordsChanged(calculateComplexWords(selectionWordCount, selectionLixLongWordCount)); emit readingTimeChanged(calculateReadingTime(selectionWordCount)); emit lixReadingEaseChanged(calculateLIX(selectionWordCount, selectionLixLongWordCount, selectionSentenceCount)); emit readabilityIndexChanged(calculateCLI(selectionWordCharacterCount, selectionWordCount, selectionSentenceCount)); } void DocumentStatistics::onTextDeselected() { updateStatistics(); } void DocumentStatistics::onTextChanged(int position, int charsRemoved, int charsAdded) { Q_UNUSED(charsRemoved) int startIndex = position - charsRemoved; if (startIndex < 0) { startIndex = 0; } int endIndex = position + charsAdded; if ((endIndex < startIndex) || (endIndex >= document->characterCount())) { endIndex = document->characterCount() - 1; } // Update the word counts of affected blocks. Note that there is no need to // check for changes to section headings, since the Highlighter class will // take care of this for us. // QTextBlock startBlock = document->findBlock(startIndex); QTextBlock endBlock = document->findBlock(endIndex); QTextBlock block = startBlock; updateBlockStatistics(block); while (block != endBlock) { block = block.next(); updateBlockStatistics(block); } updateStatistics(); } void DocumentStatistics::onTextBlockRemoved(const QTextBlock& block) { TextBlockData* blockData = (TextBlockData*) block.userData(); if (NULL != blockData) { wordCount -= blockData->wordCount; lixLongWordCount -= blockData->lixLongWordCount; wordCharacterCount -= blockData->alphaNumericCharacterCount; sentenceCount -= blockData->sentenceCount; if (!blockData->blankLine) { paragraphCount--; } updateStatistics(); } } void DocumentStatistics::updateStatistics() { emit wordCountChanged(wordCount); emit totalWordCountChanged(wordCount); emit characterCountChanged(document->characterCount() - 1); emit sentenceCountChanged(sentenceCount); emit paragraphCountChanged(paragraphCount); emit pageCountChanged(calculatePageCount(wordCount)); emit complexWordsChanged(calculateComplexWords(wordCount, lixLongWordCount)); emit readingTimeChanged(calculateReadingTime(wordCount)); emit lixReadingEaseChanged(calculateLIX(wordCount, lixLongWordCount, sentenceCount)); emit readabilityIndexChanged(calculateCLI(wordCharacterCount, wordCount, sentenceCount)); } void DocumentStatistics::updateBlockStatistics(QTextBlock& block) { TextBlockData* blockData = (TextBlockData*) block.userData(); if (NULL == blockData) { blockData = new TextBlockData(document, block); block.setUserData(blockData); } int oldWordCount = blockData->wordCount; int oldLixLongWordCount = blockData->lixLongWordCount; int oldAlphaNumCharCount = blockData->alphaNumericCharacterCount; countWords ( block.text(), blockData->wordCount, blockData->lixLongWordCount, blockData->alphaNumericCharacterCount ); wordCount += blockData->wordCount - oldWordCount; lixLongWordCount += blockData->lixLongWordCount - oldLixLongWordCount; wordCharacterCount += blockData->alphaNumericCharacterCount - oldAlphaNumCharCount; int oldSentenceCount = blockData->sentenceCount; blockData->sentenceCount = countSentences(block.text()); sentenceCount += blockData->sentenceCount - oldSentenceCount; if (blockData->blankLine) { if (block.text().trimmed().length() > 0) { blockData->blankLine = false; paragraphCount++; } } else if (block.text().trimmed().length() <= 0) { blockData->blankLine = true; paragraphCount--; } } void DocumentStatistics::countWords ( const QString& text, int& words, int& lixLongWords, int& alphaNumericCharacters ) { bool inWord = false; int separatorCount = 0; int wordLen = 0; words = 0; lixLongWords = 0; alphaNumericCharacters = 0; for (int i = 0; i < text.length(); i++) { if (text[i].isLetterOrNumber()) { inWord = true; separatorCount = 0; wordLen++; alphaNumericCharacters++; } else if (text[i].isSpace() && inWord) { inWord = false; words++; if (separatorCount > 0) { wordLen--; alphaNumericCharacters--; } separatorCount = 0; if (wordLen > 6) { lixLongWords++; } wordLen = 0; } else { // This is to handle things like double dashes (`--`) // that separate words, while still counting hyphenated // words as a single word. // separatorCount++; if (inWord) { if (separatorCount > 1) { separatorCount = 0; inWord = false; words++; wordLen--; alphaNumericCharacters--; if (wordLen > 6) { lixLongWords++; } wordLen = 0; } else { wordLen++; alphaNumericCharacters++; } } } } if (inWord) { words++; if (separatorCount > 0) { wordLen--; alphaNumericCharacters--; } if (wordLen > 6) { lixLongWords++; } } } int DocumentStatistics::countSentences(const QString& text) { int count = 0; QString trimmedText = text.trimmed(); if (trimmedText.length() > 0) { QTextBoundaryFinder boundaryFinder(QTextBoundaryFinder::Sentence, trimmedText); int nextSentencePos = 0; boundaryFinder.setPosition(0); while (nextSentencePos >= 0) { int oldPos = nextSentencePos; nextSentencePos = boundaryFinder.toNextBoundary(); if ( ((nextSentencePos - oldPos) > 1) || (((nextSentencePos - oldPos) > 0) && !trimmedText[oldPos].isSpace()) ) { count++; } } } return count; } int DocumentStatistics::calculatePageCount(int words) { return words / 250; } int DocumentStatistics::calculateCLI(int characters, int words, int sentences) { int cli = 0; if ((sentences > 0) && (words > 0)) { cli = qCeil ( (5.88 * (qreal)((float)characters / (float)words)) - (29.6 * ((qreal)sentences / (qreal)words)) - 15.8 ); if (cli < 0) { cli = 0; } } return cli; } int DocumentStatistics::calculateLIX(int totalWords, int longWords, int sentences) { int lix = 0; if ((totalWords > 0) && sentences > 0) { lix = qCeil ( ((qreal)totalWords / (qreal)sentences) + (((qreal)longWords / (qreal)totalWords) * 100.0) ); } return lix; } int DocumentStatistics::calculateComplexWords(int totalWords, int longWords) { int complexWordsPercentage = 0; if (totalWords > 0) { complexWordsPercentage = qCeil(((qreal)longWords / (qreal)totalWords) * 100.0); } return complexWordsPercentage; } int DocumentStatistics::calculateReadingTime(int words) { return words / 270; }