Preserve Arabic spelling choices

This commit is contained in:
diyaa
2026-09-13 22:33:57 +02:00
parent f5ab97e6a0
commit 3865f77dc0
4 changed files with 144 additions and 3 deletions
@@ -14,7 +14,11 @@ public struct ArabicPronunciationProcessor: Sendable {
)
}
var processedText = lyrics.precomposedStringWithCanonicalMapping
let protection = protectPreservedSpellings(
in: lyrics,
preservedSpellings: settings.preservedSpellings
)
var processedText = protection.text.precomposedStringWithCanonicalMapping
var notes = processingNotes(for: processedText, settings: settings)
if settings.tanweenPolicy == .removeWhenUnwanted {
@@ -27,6 +31,15 @@ public struct ArabicPronunciationProcessor: Sendable {
notes.append(.tanweenAdditionNeedsReview)
}
processedText = restorePreservedSpellings(
in: processedText,
replacements: protection.replacements
)
if protection.matchCount > 0 {
notes.append(.preservedSpellingsProtected(count: protection.matchCount))
}
return ArabicPronunciationProcessingResult(
text: processedText,
notes: notes
@@ -53,6 +66,81 @@ public struct ArabicPronunciationProcessor: Sendable {
}
}
private func protectPreservedSpellings(
in text: String,
preservedSpellings: [String]
) -> PreservedSpellingProtection {
let spellings = Set(
preservedSpellings.map {
$0.trimmingCharacters(in: .whitespacesAndNewlines)
}
)
.filter { !$0.isEmpty }
.sorted {
if $0.count == $1.count {
return $0 < $1
}
return $0.count > $1.count
}
var protectedText = text
var replacements: [PreservedSpellingReplacement] = []
var matchCount = 0
for (index, spelling) in spellings.enumerated() {
let occurrenceCount = protectedText.components(separatedBy: spelling).count - 1
guard occurrenceCount > 0 else {
continue
}
let placeholder = uniquePlaceholder(
for: index,
in: protectedText
)
protectedText = protectedText.replacingOccurrences(
of: spelling,
with: placeholder
)
replacements.append(
PreservedSpellingReplacement(
placeholder: placeholder,
spelling: spelling
)
)
matchCount += occurrenceCount
}
return PreservedSpellingProtection(
text: protectedText,
replacements: replacements,
matchCount: matchCount
)
}
private func uniquePlaceholder(for index: Int, in text: String) -> String {
var collisionIndex = 0
var placeholder = "[[music-assistant-preserved-\(index)]]"
while text.contains(placeholder) {
collisionIndex += 1
placeholder = "[[music-assistant-preserved-\(index)-\(collisionIndex)]]"
}
return placeholder
}
private func restorePreservedSpellings(
in text: String,
replacements: [PreservedSpellingReplacement]
) -> String {
replacements.reduce(text) { result, replacement in
result.replacingOccurrences(
of: replacement.placeholder,
with: replacement.spelling
)
}
}
private func removeTanween(from text: String) -> String {
String(text.unicodeScalars.filter { scalar in
!isTanween(scalar)
@@ -85,6 +173,17 @@ public struct ArabicPronunciationProcessor: Sendable {
}
}
private struct PreservedSpellingProtection {
let text: String
let replacements: [PreservedSpellingReplacement]
let matchCount: Int
}
private struct PreservedSpellingReplacement {
let placeholder: String
let spelling: String
}
public struct ArabicPronunciationProcessingResult: Equatable, Sendable {
public var text: String
public var notes: [ArabicPronunciationProcessingNote]
@@ -101,4 +200,5 @@ public enum ArabicPronunciationProcessingNote: Equatable, Sendable {
case pronunciationTargetedDiacriticsNeedReview
case fullTashkeelNeedsReview
case tanweenAdditionNeedsReview
case preservedSpellingsProtected(count: Int)
}