# thinkrank/1.0.0/src/admin/components/seo/utils/contentAnalysis.js

ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console &amp; Local SEO, version 1.0.0. 306 lines.

- Page: https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/seo/utils/contentAnalysis.js
- Raw: https://pluginprobe.com/plugins/thinkrank/1.0.0/raw/src/admin/components/seo/utils/contentAnalysis.js
- Modified: 2025-08-10T07:57:44+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/thinkrank/1.0.0/code/src/admin/components/seo/utils/contentAnalysis.js#L10-L20`.

```javascript
/**
 * Content Analysis Utilities
 * 
 * Utility functions for SEO content analysis, migrated from jQuery implementation
 * to maintain consistency with original functionality
 * 
 * @package ThinkRank
 * @since 1.0.0
 */

/**
 * Calculate real word count from text
 * Industry standard word counting for SEO analysis
 * 
 * @param {string} text - Text content to analyze
 * @returns {number} Word count
 */
export const calculateWordCount = (text) => {
    if (!text || typeof text !== 'string') return 0;

    // Remove extra whitespace and split by spaces
    const words = text.trim().split(/\s+/).filter(word => word.length > 0);
    return words.length;
};

/**
 * Calculate keyword density using industry standard methodology
 * 
 * @param {string} content - Content to analyze
 * @param {string} keyword - Target keyword
 * @returns {number} Keyword density percentage (rounded to 1 decimal)
 */
export const calculateKeywordDensity = (content, keyword) => {
    if (!content || !keyword) return 0;
    
    // Convert to lowercase for case-insensitive matching
    const lowerContent = content.toLowerCase();
    const lowerKeyword = keyword.toLowerCase();
    
    // Count total words
    const totalWords = calculateWordCount(content);
    if (totalWords === 0) return 0;
    
    // Count keyword occurrences (including partial matches)
    const keywordOccurrences = (lowerContent.match(new RegExp(lowerKeyword, 'g')) || []).length;
    
    // Calculate density as percentage
    const density = (keywordOccurrences / totalWords) * 100;
    return Math.round(density * 10) / 10; // Round to 1 decimal place
};

/**
 * Estimate syllable count for readability calculations
 * Simple approximation used in Flesch Reading Ease formula
 * 
 * @param {string} text - Text to analyze
 * @returns {number} Estimated syllable count
 */
export const estimateSyllables = (text) => {
    const words = text.toLowerCase().match(/\b[a-z]+\b/g) || [];
    let syllableCount = 0;
    
    words.forEach(word => {
        // Simple syllable counting heuristic
        const vowels = word.match(/[aeiouy]+/g) || [];
        let syllables = vowels.length;
        
        // Adjust for silent 'e'
        if (word.endsWith('e') && syllables > 1) {
            syllables--;
        }
        
        // Minimum 1 syllable per word
        syllableCount += Math.max(1, syllables);
    });
    
    return syllableCount;
};

/**
 * Calculate readability score using industry standard Flesch Reading Ease
 * Official formula used by academic institutions and professional tools
 * 
 * @param {string} text - Text content to analyze
 * @returns {string} Readability assessment with score
 */
export const calculateReadabilityScore = (text) => {
    if (!text) return 'No content';
    
    const words = calculateWordCount(text);
    const sentences = (text.match(/[.!?]+/g) || []).length;
    const syllables = estimateSyllables(text);
    
    if (sentences === 0 || words === 0) return 'Too short';
    
    const avgWordsPerSentence = words / sentences;
    const avgSyllablesPerWord = syllables / words;
    
    // Official Flesch Reading Ease formula
    const fleschScore = 206.835 - (1.015 * avgWordsPerSentence) - (84.6 * avgSyllablesPerWord);
    const roundedScore = Math.round(fleschScore);
    
    // Industry standard Flesch Reading Ease scale
    let level = '';
    if (roundedScore >= 90) level = 'Very Easy';
    else if (roundedScore >= 80) level = 'Easy';
    else if (roundedScore >= 70) level = 'Fairly Easy';
    else if (roundedScore >= 60) level = 'Standard';
    else if (roundedScore >= 50) level = 'Fairly Difficult';
    else if (roundedScore >= 30) level = 'Difficult';
    else level = 'Very Difficult';
    
    // Return professional format
    return `${level} (${roundedScore})`;
};

/**
 * Analyze heading structure from HTML content
 * 
 * @param {string} htmlContent - HTML content to analyze
 * @returns {Object} Heading analysis with count and structure description
 */
export const analyzeHeadingStructure = (htmlContent) => {
    if (!htmlContent) return { count: 0, structure: 'No content' };
    
    // Create a temporary div to parse HTML
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = htmlContent;
    
    // Count different heading levels
    const h1Count = tempDiv.querySelectorAll('h1').length;
    const h2Count = tempDiv.querySelectorAll('h2').length;
    const h3Count = tempDiv.querySelectorAll('h3').length;
    const h4Count = tempDiv.querySelectorAll('h4').length;
    const h5Count = tempDiv.querySelectorAll('h5').length;
    const h6Count = tempDiv.querySelectorAll('h6').length;
    
    const totalHeadings = h1Count + h2Count + h3Count + h4Count + h5Count + h6Count;
    
    let structure = '';
    if (totalHeadings === 0) {
        structure = 'No headings';
    } else if (h1Count === 0) {
        structure = `${totalHeadings} headings (No H1)`;
    } else if (h1Count > 1) {
        structure = `${totalHeadings} headings (${h1Count} H1s)`;
    } else {
        structure = `${totalHeadings} headings (Good H1)`;
    }
    
    return { count: totalHeadings, structure: structure };
};

/**
 * Calculate content quality using industry standard SEO methodology
 * Based on established content analysis best practices
 *
 * @param {string} plainText - Plain text content
 * @param {string} htmlContent - HTML content for structure analysis
 * @returns {string} Content quality assessment
 */
export const calculateContentQuality = (plainText, htmlContent) => {
    if (!plainText) return 'No content';

    const wordCount = calculateWordCount(plainText);
    let issues = [];
    let score = 0;

    // 1. Word count analysis (Industry standard: 300+ words)
    if (wordCount < 300) {
        issues.push('Too short');
        score += 0;
    } else if (wordCount >= 300 && wordCount < 600) {
        score += 50;
    } else {
        score += 100; // 600+ words is excellent
    }

    // 2. Paragraph length analysis (SEO best practice: avoid long paragraphs)
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = htmlContent;
    const paragraphs = tempDiv.querySelectorAll('p');
    let longParagraphs = 0;

    paragraphs.forEach(p => {
        const pText = p.textContent || p.innerText || '';
        const pWordCount = calculateWordCount(pText);
        if (pWordCount > 150) { // Industry standard: flag paragraphs over 150 words
            longParagraphs++;
        }
    });

    if (longParagraphs === 0) {
        score += 100;
    } else if (longParagraphs <= 2) {
        score += 50;
        issues.push('Some long paragraphs');
    } else {
        score += 0;
        issues.push('Too many long paragraphs');
    }

    // 3. Subheading distribution (SEO best practice: subheadings every 300 words)
    const headings = tempDiv.querySelectorAll('h2, h3, h4, h5, h6').length;
    const expectedHeadings = Math.floor(wordCount / 300);

    if (headings >= expectedHeadings && headings > 0) {
        score += 100;
    } else if (headings > 0) {
        score += 50;
        issues.push('Could use more subheadings');
    } else {
        score += 0;
        issues.push('No subheadings');
    }

    // Calculate final score (0-100)
    const finalScore = Math.round(score / 3); // Average of 3 criteria

    // Return professional assessment
    if (finalScore >= 80) return 'Good';
    else if (finalScore >= 50) return 'OK';
    else return 'Needs improvement';
};

/**
 * Calculate keyword optimization using industry standard SEO methodology
 * Based on established SEO best practices
 *
 * @param {string} plainText - Plain text content
 * @param {string} htmlContent - HTML content for structure analysis
 * @param {string} title - Post title
 * @param {string} targetKeyword - Focus keyword
 * @param {string} metaDescription - Meta description
 * @returns {string} Keyword optimization assessment
 */
export const calculateKeywordOptimization = (plainText, htmlContent, title, targetKeyword, metaDescription = '') => {
    if (!targetKeyword || !targetKeyword.trim()) {
        return 'No focus keyword';
    }

    if (!plainText) return 'No content';

    const keywordLower = targetKeyword.toLowerCase();
    const contentLower = plainText.toLowerCase();
    const titleLower = title.toLowerCase();

    let checks = 0;
    let totalChecks = 0;

    // 1. Keyword in title (Essential SEO check)
    totalChecks++;
    if (titleLower.includes(keywordLower)) {
        checks++;
    }

    // 2. Keyword density (Industry standard: 0.5% - 2.5%)
    totalChecks++;
    const density = calculateKeywordDensity(plainText, targetKeyword);
    if (density >= 0.5 && density <= 2.5) {
        checks++;
    }

    // 3. Keyword in first paragraph (SEO best practice)
    totalChecks++;
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = htmlContent;
    const firstParagraph = tempDiv.querySelector('p');
    if (firstParagraph) {
        const firstParagraphText = (firstParagraph.textContent || firstParagraph.innerText || '').toLowerCase();
        if (firstParagraphText.includes(keywordLower)) {
            checks++;
        }
    }

    // 4. Keyword in subheadings (Content structure optimization)
    totalChecks++;
    const subheadings = tempDiv.querySelectorAll('h2, h3, h4, h5, h6');
    let keywordInSubheading = false;
    subheadings.forEach(heading => {
        const headingText = (heading.textContent || heading.innerText || '').toLowerCase();
        if (headingText.includes(keywordLower)) {
            keywordInSubheading = true;
        }
    });
    if (keywordInSubheading) {
        checks++;
    }

    // 5. Keyword in meta description (Meta optimization)
    if (metaDescription) {
        totalChecks++;
        if (metaDescription.toLowerCase().includes(keywordLower)) {
            checks++;
        }
    }

    // Calculate score based on checks passed
    const percentage = Math.round((checks / totalChecks) * 100);

    // Return professional assessment
    if (percentage >= 80) return 'Good';
    else if (percentage >= 60) return 'OK';
    else return 'Needs improvement';
};

```
