package enrich import ( "strings" "unicode" ) // PoeticAnalyzer analyzes the poetic structure of text type PoeticAnalyzer struct{} // NewPoeticAnalyzer creates a new PoeticAnalyzer func NewPoeticAnalyzer() *PoeticAnalyzer { return &PoeticAnalyzer{} } // Analyse analyzes the poetic structure of text and returns metrics func (a *PoeticAnalyzer) Analyse(text Text) (PoeticMetrics, error) { // This is a simplified implementation // In a real-world scenario, you would use a more sophisticated approach content := text.Body // Split into lines lines := strings.Split(content, "\n") // Count non-empty lines var nonEmptyLines []string for _, line := range lines { if strings.TrimSpace(line) != "" { nonEmptyLines = append(nonEmptyLines, line) } } // Count stanzas (groups of lines separated by blank lines) stanzaCount := 0 inStanza := false for _, line := range lines { if strings.TrimSpace(line) == "" { if inStanza { inStanza = false } } else { if !inStanza { inStanza = true stanzaCount++ } } } // Ensure at least one stanza if there are lines if len(nonEmptyLines) > 0 && stanzaCount == 0 { stanzaCount = 1 } // Detect rhyme scheme rhymeScheme := detectRhymeScheme(nonEmptyLines) // Detect meter type meterType := detectMeterType(nonEmptyLines) // Determine structure structure := determineStructure(stanzaCount, len(nonEmptyLines), rhymeScheme, meterType) metrics := PoeticMetrics{ RhymeScheme: rhymeScheme, MeterType: meterType, StanzaCount: stanzaCount, LineCount: len(nonEmptyLines), Structure: structure, } return metrics, nil } // detectRhymeScheme detects the rhyme scheme of a poem func detectRhymeScheme(lines []string) string { // This is a simplified implementation // In a real-world scenario, you would use phonetic analysis if len(lines) < 2 { return "Unknown" } // Extract last word of each line lastWords := make([]string, len(lines)) for i, line := range lines { words := strings.Fields(line) if len(words) > 0 { lastWords[i] = strings.ToLower(words[len(words)-1]) // Remove punctuation lastWords[i] = strings.TrimFunc(lastWords[i], func(r rune) bool { return !unicode.IsLetter(r) }) } } // Check for common rhyme schemes // Check for AABB pattern if len(lines) >= 4 { if endsMatch(lastWords[0], lastWords[1]) && endsMatch(lastWords[2], lastWords[3]) { return "AABB" } } // Check for ABAB pattern if len(lines) >= 4 { if endsMatch(lastWords[0], lastWords[2]) && endsMatch(lastWords[1], lastWords[3]) { return "ABAB" } } // Check for ABBA pattern if len(lines) >= 4 { if endsMatch(lastWords[0], lastWords[3]) && endsMatch(lastWords[1], lastWords[2]) { return "ABBA" } } // Check for AAAA pattern if len(lines) >= 4 { if endsMatch(lastWords[0], lastWords[1]) && endsMatch(lastWords[1], lastWords[2]) && endsMatch(lastWords[2], lastWords[3]) { return "AAAA" } } // Default return "Irregular" } // detectMeterType detects the meter type of a poem func detectMeterType(lines []string) string { // This is a simplified implementation // In a real-world scenario, you would use syllable counting and stress patterns if len(lines) == 0 { return "Unknown" } // Count syllables in each line syllableCounts := make([]int, len(lines)) for i, line := range lines { syllableCounts[i] = countSyllables(line) } // Check for common meter types // Check for iambic pentameter (around 10 syllables per line) pentameterCount := 0 for _, count := range syllableCounts { if count >= 9 && count <= 11 { pentameterCount++ } } if float64(pentameterCount)/float64(len(lines)) > 0.7 { return "Iambic Pentameter" } // Check for tetrameter (around 8 syllables per line) tetrameterCount := 0 for _, count := range syllableCounts { if count >= 7 && count <= 9 { tetrameterCount++ } } if float64(tetrameterCount)/float64(len(lines)) > 0.7 { return "Tetrameter" } // Check for trimeter (around 6 syllables per line) trimeterCount := 0 for _, count := range syllableCounts { if count >= 5 && count <= 7 { trimeterCount++ } } if float64(trimeterCount)/float64(len(lines)) > 0.7 { return "Trimeter" } // Default return "Free Verse" } // determineStructure determines the overall structure of a poem func determineStructure(stanzaCount, lineCount int, rhymeScheme, meterType string) string { // This is a simplified implementation // Check for common poetic forms // Sonnet if lineCount == 14 && (rhymeScheme == "ABAB" || rhymeScheme == "ABBA") && meterType == "Iambic Pentameter" { return "Sonnet" } // Haiku if lineCount == 3 && stanzaCount == 1 { return "Haiku" } // Limerick if lineCount == 5 && stanzaCount == 1 && rhymeScheme == "AABBA" { return "Limerick" } // Quatrain if lineCount%4 == 0 && stanzaCount == lineCount/4 { return "Quatrain" } // Tercet if lineCount%3 == 0 && stanzaCount == lineCount/3 { return "Tercet" } // Couplet if lineCount%2 == 0 && stanzaCount == lineCount/2 { return "Couplet" } // Default return "Free Form" } // endsMatch checks if two words have matching endings (simplified rhyme detection) func endsMatch(word1, word2 string) bool { // This is a simplified implementation // In a real-world scenario, you would use phonetic analysis if len(word1) < 2 || len(word2) < 2 { return false } // Check if the last 2 characters match return word1[len(word1)-2:] == word2[len(word2)-2:] } // countSyllables counts the syllables in a line of text func countSyllables(line string) int { // This is a simplified implementation // In a real-world scenario, you would use a dictionary or ML model words := strings.Fields(line) syllableCount := 0 for _, word := range words { // Clean the word word = strings.ToLower(word) word = strings.TrimFunc(word, func(r rune) bool { return !unicode.IsLetter(r) }) // Count vowel groups inVowelGroup := false for i, r := range word { isVowelChar := isVowel(r) // Start of vowel group if isVowelChar && !inVowelGroup { inVowelGroup = true syllableCount++ } // End of vowel group if !isVowelChar { inVowelGroup = false } // Handle silent e at the end if i == len(word)-1 && r == 'e' && i > 0 { prevChar := rune(word[i-1]) if !isVowel(prevChar) { syllableCount-- } } } // Ensure at least one syllable per word if syllableCount == 0 { syllableCount = 1 } } return syllableCount } // isVowel checks if a character is a vowel func isVowel(r rune) bool { return r == 'a' || r == 'e' || r == 'i' || r == 'o' || r == 'u' || r == 'y' }