From d8b06e93670cd8f0869597a3c658e1cd973ad583 Mon Sep 17 00:00:00 2001 From: Miwa <63481257+ensan-hcl@users.noreply.github.com> Date: Wed, 23 Jul 2025 23:35:36 +0900 Subject: [PATCH] Merge pull request #227 from azooKey/feat/mapped_input_style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: `.mapped(id)`を新たな入力スタイルとして導入し、カスタムローマ字かな変換テーブルに対応 --- .../ConversionAlgorithms/Prediction.swift | 9 +- .../ConverterAPI/KanaKanjiConverter.swift | 6 +- .../DictionaryManagement/DicdataStore.swift | 11 - .../DictionaryManagement/TypoCorrection.swift | 20 +- .../InputManagement/ComposingText.swift | 32 +- .../InputManagement/InputStyle.swift | 12 + .../InputManagement/InputStyleManager.swift | 101 ++ .../InputManagement/InputTableID.swift | 8 + .../InputManagement/Roman2Kana.swift | 349 ------- .../InputManagement/Roman2KanaMaps.swift | 888 ++++++++++++++++++ .../InputManagement/States.swift | 7 - Sources/SwiftUtils/CharacterUtils.swift | 5 - .../ComposingTextTests.swift | 2 +- .../InputStyleManagerTests.swift | 28 + .../Roman2KanaTests.swift | 25 +- .../ConverterTests/ConverterTests.swift | 23 + .../DicdataStoreTests/DicdataStoreTests.swift | 2 +- .../SwiftUtilsTests/CharacterUtilsTests.swift | 12 - 18 files changed, 1126 insertions(+), 414 deletions(-) create mode 100644 Sources/KanaKanjiConverterModule/InputManagement/InputStyle.swift create mode 100644 Sources/KanaKanjiConverterModule/InputManagement/InputStyleManager.swift create mode 100644 Sources/KanaKanjiConverterModule/InputManagement/InputTableID.swift delete mode 100644 Sources/KanaKanjiConverterModule/InputManagement/Roman2Kana.swift create mode 100644 Sources/KanaKanjiConverterModule/InputManagement/Roman2KanaMaps.swift create mode 100644 Tests/KanaKanjiConverterModuleTests/InputStyleManagerTests.swift diff --git a/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Prediction.swift b/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Prediction.swift index b769355..6bbae3a 100644 --- a/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Prediction.swift +++ b/Sources/KanaKanjiConverterModule/ConversionAlgorithms/Prediction.swift @@ -62,7 +62,12 @@ extension Kana2Kanji { switch inputStyle { case .direct: dicdata = self.dicdataStore.getPredictionLOUDSDicdata(key: lastRuby) - case .roman2kana: + case .roman2kana, .mapped: + let table = if case let .mapped(id) = inputStyle { + InputStyleManager.shared.table(for: id) + } else { + InputStyleManager.shared.table(for: .defaultRomanToKana) + } let roman = lastRuby.suffix(while: {String($0).onlyRomanAlphabet}) if !roman.isEmpty { let ruby: Substring = lastRuby.dropLast(roman.count) @@ -70,7 +75,7 @@ extension Kana2Kanji { dicdata = [] break } - let possibleNexts: [Substring] = DicdataStore.possibleNexts[String(roman), default: []].map {ruby + $0} + let possibleNexts: [Substring] = table.possibleNexts[String(roman), default: []].map {ruby + $0} debug(#function, lastRuby, ruby, roman, possibleNexts, prepart, lastRubyCount) dicdata = possibleNexts.flatMap { self.dicdataStore.getPredictionLOUDSDicdata(key: $0) } } else { diff --git a/Sources/KanaKanjiConverterModule/ConverterAPI/KanaKanjiConverter.swift b/Sources/KanaKanjiConverterModule/ConverterAPI/KanaKanjiConverter.swift index ba860ec..1a45925 100644 --- a/Sources/KanaKanjiConverterModule/ConverterAPI/KanaKanjiConverter.swift +++ b/Sources/KanaKanjiConverterModule/ConverterAPI/KanaKanjiConverter.swift @@ -336,10 +336,8 @@ import EfficientNGram /// 付加的な変換候補 private func getTopLevelAdditionalCandidate(_ inputData: ComposingText, options: ConvertRequestOptions) -> [Candidate] { var candidates: [Candidate] = [] - if inputData.input.allSatisfy({$0.inputStyle == .roman2kana}) { - if options.englishCandidateInRoman2KanaInput { - candidates.append(contentsOf: self.getForeignPredictionCandidate(inputData: inputData, language: "en-US", penalty: -10)) - } + if options.englishCandidateInRoman2KanaInput, inputData.input.allSatisfy({$0.character.isASCII}) { + candidates.append(contentsOf: self.getForeignPredictionCandidate(inputData: inputData, language: "en-US", penalty: -10)) } return candidates } diff --git a/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift b/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift index 772f7db..9bc376e 100644 --- a/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift +++ b/Sources/KanaKanjiConverterModule/DictionaryManagement/DicdataStore.swift @@ -1027,15 +1027,4 @@ public final class DicdataStore { return true } - - static let possibleNexts: [String: [String]] = { - var results: [String: [String]] = [:] - for (key, value) in Roman2Kana.katakanaChanges { - for prefixCount in 0 ..< key.count where 0 < prefixCount { - let prefix = String(key.prefix(prefixCount)) - results[prefix, default: []].append(value) - } - } - return results - }() } diff --git a/Sources/KanaKanjiConverterModule/DictionaryManagement/TypoCorrection.swift b/Sources/KanaKanjiConverterModule/DictionaryManagement/TypoCorrection.swift index 1a60070..7ac0427 100644 --- a/Sources/KanaKanjiConverterModule/DictionaryManagement/TypoCorrection.swift +++ b/Sources/KanaKanjiConverterModule/DictionaryManagement/TypoCorrection.swift @@ -97,9 +97,14 @@ struct TypoCorrectionGenerator: Sendable { switch item.inputStyle { case .direct: stablePrefix.append(contentsOf: item.string) - case .roman2kana: + case .roman2kana, .mapped: + let table = if case let .mapped(id) = item.inputStyle { + InputStyleManager.shared.table(for: id) + } else { + InputStyleManager.shared.table(for: .defaultRomanToKana) + } var stableIndex = item.string.endIndex - for suffix in Roman2Kana.unstableSuffixes { + for suffix in table.unstableSuffixes { if item.string.hasSuffix(suffix) { stableIndex = min(stableIndex, item.string.endIndex - suffix.count) } @@ -197,7 +202,7 @@ struct TypoCorrectionGenerator: Sendable { return result } } - if (elements.allSatisfy {$0.inputStyle == .roman2kana}) { + if (elements.allSatisfy {$0.inputStyle == .roman2kana || $0.inputStyle == .mapped(id: .defaultRomanToKana)}) { let dictionary: [String: [TypoCandidate]] = frozen ? [:] : Self.roman2KanaPossibleTypo if key.count > 1 { return dictionary[key, default: []] @@ -210,7 +215,14 @@ struct TypoCorrectionGenerator: Sendable { return result } } - return [] + // `.mapped`や、混ざっているケースでここに到達する + return if elements.count == 1 { + [ + TypoCandidate(inputElements: [elements.first!], weight: 0) + ] + } else { + [] + } } fileprivate static let lengths = [0, 1] diff --git a/Sources/KanaKanjiConverterModule/InputManagement/ComposingText.swift b/Sources/KanaKanjiConverterModule/InputManagement/ComposingText.swift index 32fe406..1f182e9 100644 --- a/Sources/KanaKanjiConverterModule/InputManagement/ComposingText.swift +++ b/Sources/KanaKanjiConverterModule/InputManagement/ComposingText.swift @@ -185,10 +185,17 @@ public struct ComposingText: Sendable { // 例えばcovnertTargetが「あき|ょ」で、`[a, k, y, o]`まで見て「あきょ」になってしまった場合、「あき」がprefixとなる。 // この場合、lastPrefix=1なので、1番目から現在までの入力をひらがな(suffix)で置き換える else if converted.hasPrefix(target) { + // lastPrefixIndex: 「あ」までなので1 + // count: 「あきょ」までなので4 + // replaceCount: 3 let replaceCount = count - lastPrefixIndex + // suffix: 「あきょ」から「あ」を落とした分なので、「きょ」 let suffix = converted.suffix(converted.count - lastPrefix.count) + // lastPrefixIndexから現在のカウントまでをReplace self.input.removeSubrange(count - replaceCount ..< count) - self.input.insert(contentsOf: suffix.map {InputElement(character: $0, inputStyle: CharacterUtils.isRomanLetter($0) ? .roman2kana : .direct)}, at: count - replaceCount) + // suffix1文字ずつを入力に追加する + // この結果として生じる文字列については、`frozen`で処理する + self.input.insert(contentsOf: suffix.map {InputElement(character: $0, inputStyle: .frozen)}, at: count - replaceCount) count -= replaceCount count += suffix.count @@ -436,7 +443,9 @@ extension ComposingText { case .direct: return current + [newCharacter] case .roman2kana: - return Roman2Kana.toHiragana(currentText: current, added: newCharacter) + return InputStyleManager.shared.table(for: .defaultRomanToKana).toHiragana(currentText: current, added: newCharacter) + case .mapped(let id): + return InputStyleManager.shared.table(for: id).toHiragana(currentText: current, added: newCharacter) } } @@ -445,7 +454,9 @@ extension ComposingText { case .direct: convertTarget.append(newCharacter) case .roman2kana: - convertTarget = Roman2Kana.toHiragana(currentText: convertTarget, added: newCharacter) + convertTarget = InputStyleManager.shared.table(for: .defaultRomanToKana).toHiragana(currentText: convertTarget, added: newCharacter) + case .mapped(let id): + convertTarget = InputStyleManager.shared.table(for: id).toHiragana(currentText: convertTarget, added: newCharacter) } } @@ -486,9 +497,11 @@ extension ComposingText.InputElement: CustomDebugStringConvertible { public var debugDescription: String { switch self.inputStyle { case .direct: - return "direct(\(character))" + "direct(\(character))" case .roman2kana: - return "roman2kana(\(character))" + "roman2kana(\(character))" + case .mapped(let id): + "mapped(\(id); \(character))" } } } @@ -500,7 +513,14 @@ extension ComposingText.ConvertTargetElement: CustomDebugStringConvertible { } extension InputStyle: CustomDebugStringConvertible { public var debugDescription: String { - "." + self.rawValue + switch self { + case .direct: + ".direct" + case .roman2kana: + ".roman2kana" + case .mapped(let id): + ".mapped(\(id))" + } } } #endif diff --git a/Sources/KanaKanjiConverterModule/InputManagement/InputStyle.swift b/Sources/KanaKanjiConverterModule/InputManagement/InputStyle.swift new file mode 100644 index 0000000..bd0fbdc --- /dev/null +++ b/Sources/KanaKanjiConverterModule/InputManagement/InputStyle.swift @@ -0,0 +1,12 @@ +public enum InputStyle: Sendable, Equatable, Hashable { + /// 入力された文字を直接入力するスタイル + case direct + /// ローマ字日本語入力とするスタイル + case roman2kana + /// カスタムローマ字かな変換テーブルなど、任意のマッピングを管理 + case mapped(id: InputTableID) + + static var frozen: Self { + .mapped(id: .empty) + } +} diff --git a/Sources/KanaKanjiConverterModule/InputManagement/InputStyleManager.swift b/Sources/KanaKanjiConverterModule/InputManagement/InputStyleManager.swift new file mode 100644 index 0000000..5d23d85 --- /dev/null +++ b/Sources/KanaKanjiConverterModule/InputManagement/InputStyleManager.swift @@ -0,0 +1,101 @@ +import Foundation +import SwiftUtils + +final class InputStyleManager { + nonisolated(unsafe) static let shared = InputStyleManager() + + struct Table { + init(hiraganaChanges: [[Character] : [Character]]) { + self.hiraganaChanges = hiraganaChanges + self.unstableSuffixes = hiraganaChanges.keys.flatMapSet { characters in + characters.indices.map { i in + Array(characters[...i]) + } + } + let katakanaChanges = Dictionary(uniqueKeysWithValues: hiraganaChanges.map { (String($0.key), String($0.value).toKatakana()) }) + self.katakanaChanges = katakanaChanges + self.maxKeyCount = hiraganaChanges.lazy.map { $0.key.count }.max() ?? 0 + self.possibleNexts = { + var results: [String: [String]] = [:] + for (key, value) in katakanaChanges { + for prefixCount in 0 ..< key.count where 0 < prefixCount { + let prefix = String(key.prefix(prefixCount)) + results[prefix, default: []].append(value) + } + } + return results + }() + } + + let unstableSuffixes: Set<[Character]> + let katakanaChanges: [String: String] + let hiraganaChanges: [[Character]: [Character]] + let maxKeyCount: Int + let possibleNexts: [String: [String]] + + static let empty = Table(hiraganaChanges: [:]) + + func toHiragana(currentText: [Character], added: Character) -> [Character] { + for n in (0 ..< self.maxKeyCount).reversed() { + if n == 0 { + if let kana = self.hiraganaChanges[[added]] { + return currentText + kana + } + } else { + let last = currentText.suffix(n) + if let kana = self.hiraganaChanges[last + [added]] { + return currentText.prefix(currentText.count - last.count) + kana + } + } + } + return currentText + [added] + } + } + + private var tables: [InputTableID: Table] = [:] + + private init() { + // デフォルトのテーブルは最初から追加しておく + let defaultRomanToKana = Table(hiraganaChanges: Roman2KanaMaps.defaultRomanToKanaMap) + let defaultAZIK = Table(hiraganaChanges: Roman2KanaMaps.defaultAzikMap) + self.tables = [ + .empty: .empty, + .defaultRomanToKana: defaultRomanToKana, + .defaultAZIK: defaultAZIK + ] + } + + func table(for id: InputTableID) -> Table { + switch id { + case .defaultRomanToKana, .defaultAZIK, .empty: + return self.tables[id]! + case .custom(let url): + if let table = self.tables[id] { + return table + } else if let table = try? Self.loadTable(from: url) { + self.tables[id] = table + return table + } else { + return .empty + } + } + } + + private static func loadTable(from url: URL) throws -> Table { + let content = try String(contentsOf: url, encoding: .utf8) + var map: [[Character]: [Character]] = [:] + for line in content.components(separatedBy: .newlines) { + // 空行は無視 + guard !line.trimmingCharacters(in: .whitespaces).isEmpty else { continue } + // `# `で始まる行はコメントとして明示的に無視 + guard !line.hasPrefix("# ") else { continue } + let cols = line.split(separator: "\t") + // 要素の無い行は無視 + guard cols.count >= 2 else { continue } + let key = Array(String(cols[0])) + let value = Array(String(cols[1])) + map[key] = value + } + return Table(hiraganaChanges: map) + } +} diff --git a/Sources/KanaKanjiConverterModule/InputManagement/InputTableID.swift b/Sources/KanaKanjiConverterModule/InputManagement/InputTableID.swift new file mode 100644 index 0000000..adbbcc3 --- /dev/null +++ b/Sources/KanaKanjiConverterModule/InputManagement/InputTableID.swift @@ -0,0 +1,8 @@ +public import struct Foundation.URL + +public enum InputTableID: Sendable, Equatable, Hashable { + case defaultRomanToKana + case defaultAZIK + case empty + case custom(URL) +} diff --git a/Sources/KanaKanjiConverterModule/InputManagement/Roman2Kana.swift b/Sources/KanaKanjiConverterModule/InputManagement/Roman2Kana.swift deleted file mode 100644 index 043643f..0000000 --- a/Sources/KanaKanjiConverterModule/InputManagement/Roman2Kana.swift +++ /dev/null @@ -1,349 +0,0 @@ -// -// Roman2Kana.swift -// Keyboard -// -// Created by ensan on 2020/09/24. -// Copyright © 2020 ensan. All rights reserved. -// - -import Foundation -import SwiftUtils - -enum Roman2Kana { - static let unstableSuffixes: Set<[Character]> = hiraganaChanges.keys.flatMapSet { characters in - characters.indices.map { i in - Array(characters[...i]) - } - } - static let katakanaChanges: [String: String] = Dictionary(uniqueKeysWithValues: hiraganaChanges.map { (String($0.key), String($0.value).toKatakana()) }) - static let hiraganaChanges: [[Character]: [Character]] = Dictionary(uniqueKeysWithValues: [ - "a": "あ", - "xa": "ぁ", - "la": "ぁ", - "i": "い", - "xi": "ぃ", - "li": "ぃ", - "u": "う", - "wu": "う", - "vu": "ゔ", - "xu": "ぅ", - "lu": "ぅ", - "e": "え", - "xe": "ぇ", - "le": "ぇ", - "o": "お", - "xo": "ぉ", - "lo": "ぉ", - "ka": "か", - "ca": "か", - "ga": "が", - "xka": "ゕ", - "lka": "ゕ", - "ki": "き", - "gi": "ぎ", - "ku": "く", - "cu": "く", - "gu": "ぐ", - "ke": "け", - "ge": "げ", - "xke": "ゖ", - "lke": "ゖ", - "ko": "こ", - "co": "こ", - "go": "ご", - "sa": "さ", - "za": "ざ", - "si": "し", - "ci": "し", - "shi": "し", - "zi": "じ", - "ji": "じ", - "su": "す", - "zu": "ず", - "se": "せ", - "ce": "せ", - "ze": "ぜ", - "so": "そ", - "zo": "ぞ", - "ta": "た", - "da": "だ", - "ti": "ち", - "chi": "ち", - "di": "ぢ", - "tu": "つ", - "tsu": "つ", - "xtu": "っ", - "ltu": "っ", - "xtsu": "っ", - "ltsu": "っ", - "du": "づ", - "te": "て", - "de": "で", - "to": "と", - "do": "ど", - "na": "な", - "ni": "に", - "nu": "ぬ", - "ne": "ね", - "no": "の", - "ha": "は", - "ba": "ば", - "pa": "ぱ", - "hi": "ひ", - "bi": "び", - "pi": "ぴ", - "hu": "ふ", - "fu": "ふ", - "bu": "ぶ", - "pu": "ぷ", - "he": "へ", - "be": "べ", - "pe": "ぺ", - "ho": "ほ", - "bo": "ぼ", - "po": "ぽ", - "ma": "ま", - "mi": "み", - "mu": "む", - "me": "め", - "mo": "も", - "ya": "や", - "xya": "ゃ", - "lya": "ゃ", - "yu": "ゆ", - "xyu": "ゅ", - "lyu": "ゅ", - "yo": "よ", - "xyo": "ょ", - "lyo": "ょ", - "ra": "ら", - "ri": "り", - "ru": "る", - "re": "れ", - "ro": "ろ", - "wa": "わ", - "xwa": "ゎ", - "lwa": "ゎ", - "wyi": "ゐ", - "wye": "ゑ", - "wo": "を", - "nn": "ん", - "ye": "いぇ", - "va": "ゔぁ", - "vi": "ゔぃ", - "ve": "ゔぇ", - "vo": "ゔぉ", - "kya": "きゃ", - "kyu": "きゅ", - "kye": "きぇ", - "kyo": "きょ", - "gya": "ぎゃ", - "gyu": "ぎゅ", - "gye": "ぎぇ", - "gyo": "ぎょ", - "qa": "くぁ", - "kwa": "くぁ", - "qwa": "くぁ", - "qi": "くぃ", - "kwi": "くぃ", - "qwi": "くぃ", - "qu": "くぅ", - "kwu": "くぅ", - "qwu": "くぅ", - "qe": "くぇ", - "kwe": "くぇ", - "qwe": "くぇ", - "qo": "くぉ", - "kwo": "くぉ", - "qwo": "くぉ", - "gwa": "ぐぁ", - "gwi": "ぐぃ", - "gwu": "ぐぅ", - "gwe": "ぐぇ", - "gwo": "ぐぉ", - "sha": "しゃ", - "sya": "しゃ", - "shu": "しゅ", - "syu": "しゅ", - "she": "しぇ", - "sye": "しぇ", - "sho": "しょ", - "syo": "しょ", - "ja": "じゃ", - "zya": "じゃ", - "jya": "じゃ", - "jyi": "じぃ", - "ju": "じゅ", - "zyu": "じゅ", - "jyu": "じゅ", - "je": "じぇ", - "zye": "じぇ", - "jye": "じぇ", - "jo": "じょ", - "zyo": "じょ", - "jyo": "じょ", - "swa": "すぁ", - "swi": "すぃ", - "swu": "すぅ", - "swe": "すぇ", - "swo": "すぉ", - "cha": "ちゃ", - "cya": "ちゃ", - "tya": "ちゃ", - "tyi": "ちぃ", - "cyi": "ちぃ", - "chu": "ちゅ", - "cyu": "ちゅ", - "tyu": "ちゅ", - "che": "ちぇ", - "cye": "ちぇ", - "tye": "ちぇ", - "cho": "ちょ", - "cyo": "ちょ", - "tyo": "ちょ", - "tsa": "つぁ", - "tsi": "つぃ", - "tse": "つぇ", - "tso": "つぉ", - "tha": "てゃ", - "thi": "てぃ", - "thu": "てゅ", - "the": "てぇ", - "tho": "てょ", - "twa": "とぁ", - "twi": "とぃ", - "twu": "とぅ", - "twe": "とぇ", - "two": "とぉ", - "dya": "ぢゃ", - "dyi": "ぢぃ", - "dyu": "ぢゅ", - "dye": "ぢぇ", - "dyo": "ぢょ", - "dha": "でゃ", - "dhi": "でぃ", - "dhu": "でゅ", - "dhe": "でぇ", - "dho": "でょ", - "dwa": "どぁ", - "dwi": "どぃ", - "dwu": "どぅ", - "dwe": "どぇ", - "dwo": "どぉ", - "nya": "にゃ", - "nyi": "にぃ", - "nyu": "にゅ", - "nye": "にぇ", - "nyo": "にょ", - "hya": "ひゃ", - "hyi": "ひぃ", - "hyu": "ひゅ", - "hye": "ひぇ", - "hyo": "ひょ", - "bya": "びゃ", - "byi": "びぃ", - "byu": "びゅ", - "bye": "びぇ", - "byo": "びょ", - "pya": "ぴゃ", - "pyi": "ぴぃ", - "pyu": "ぴゅ", - "pye": "ぴぇ", - "pyo": "ぴょ", - "fa": "ふぁ", - "hwa": "ふぁ", - "fwa": "ふぁ", - "fi": "ふぃ", - "hwi": "ふぃ", - "fwi": "ふぃ", - "fwu": "ふぅ", - "fe": "ふぇ", - "hwe": "ふぇ", - "fwe": "ふぇ", - "fo": "ふぉ", - "hwo": "ふぉ", - "fwo": "ふぉ", - "fya": "ふゃ", - "fyu": "ふゅ", - "fyo": "ふょ", - "mya": "みゃ", - "myi": "みぃ", - "myu": "みゅ", - "mye": "みぇ", - "myo": "みょ", - "rya": "りゃ", - "ryi": "りぃ", - "ryu": "りゅ", - "rye": "りぇ", - "ryo": "りょ", - "wi": "うぃ", - "we": "うぇ", - "wha": "うぁ", - "whi": "うぃ", - "whu": "う", - "whe": "うぇ", - "who": "うぉ", - "bb": "っb", - "cc": "っc", - "dd": "っd", - "ff": "っf", - "gg": "っg", - "hh": "っh", - "jj": "っj", - "kk": "っk", - "ll": "っl", - "mm": "っm", - "pp": "っp", - "qq": "っq", - "rr": "っr", - "ss": "っs", - "tt": "っt", - "vv": "っv", - "ww": "っw", - "xx": "っx", - "yy": "っy", - "zz": "っz", - "nb": "んb", - "nc": "んc", - "nd": "んd", - "nf": "んf", - "ng": "んg", - "nh": "んh", - "nj": "んj", - "nk": "んk", - "nl": "んl", - "nm": "んm", - "np": "んp", - "nq": "んq", - "nr": "んr", - "ns": "んs", - "nt": "んt", - "nv": "んv", - "nw": "んw", - "nx": "んx", - "nz": "んz", - "xn": "ん", - "zh": "←", - "zj": "↓", - "zk": "↑", - "zl": "→" - ].map {(Array($0.key), Array($0.value))}) - - static let maxKeyCount = hiraganaChanges.lazy.map { $0.key.count }.max() ?? 0 - - static func toHiragana(currentText: [Character], added: Character) -> [Character] { - for n in (0 ..< maxKeyCount).reversed() { - if n == 0 { - if let kana = Roman2Kana.hiraganaChanges[[added]] { - return currentText + kana - } - } else { - let last = currentText.suffix(n) - if let kana = Roman2Kana.hiraganaChanges[last + [added]] { - return currentText.prefix(currentText.count - last.count) + kana - } - } - } - return currentText + [added] - } -} diff --git a/Sources/KanaKanjiConverterModule/InputManagement/Roman2KanaMaps.swift b/Sources/KanaKanjiConverterModule/InputManagement/Roman2KanaMaps.swift new file mode 100644 index 0000000..c1362df --- /dev/null +++ b/Sources/KanaKanjiConverterModule/InputManagement/Roman2KanaMaps.swift @@ -0,0 +1,888 @@ +import Foundation + +enum Roman2KanaMaps { + static let defaultRomanToKanaMap: [[Character]: [Character]] = Dictionary(uniqueKeysWithValues: [ + "a": "あ", + "xa": "ぁ", + "la": "ぁ", + "i": "い", + "xi": "ぃ", + "li": "ぃ", + "u": "う", + "wu": "う", + "vu": "ゔ", + "xu": "ぅ", + "lu": "ぅ", + "e": "え", + "xe": "ぇ", + "le": "ぇ", + "o": "お", + "xo": "ぉ", + "lo": "ぉ", + "ka": "か", + "ca": "か", + "ga": "が", + "xka": "ゕ", + "lka": "ゕ", + "ki": "き", + "gi": "ぎ", + "ku": "く", + "cu": "く", + "gu": "ぐ", + "ke": "け", + "ge": "げ", + "xke": "ゖ", + "lke": "ゖ", + "ko": "こ", + "co": "こ", + "go": "ご", + "sa": "さ", + "za": "ざ", + "si": "し", + "ci": "し", + "shi": "し", + "zi": "じ", + "ji": "じ", + "su": "す", + "zu": "ず", + "se": "せ", + "ce": "せ", + "ze": "ぜ", + "so": "そ", + "zo": "ぞ", + "ta": "た", + "da": "だ", + "ti": "ち", + "chi": "ち", + "di": "ぢ", + "tu": "つ", + "tsu": "つ", + "xtu": "っ", + "ltu": "っ", + "xtsu": "っ", + "ltsu": "っ", + "du": "づ", + "te": "て", + "de": "で", + "to": "と", + "do": "ど", + "na": "な", + "ni": "に", + "nu": "ぬ", + "ne": "ね", + "no": "の", + "ha": "は", + "ba": "ば", + "pa": "ぱ", + "hi": "ひ", + "bi": "び", + "pi": "ぴ", + "hu": "ふ", + "fu": "ふ", + "bu": "ぶ", + "pu": "ぷ", + "he": "へ", + "be": "べ", + "pe": "ぺ", + "ho": "ほ", + "bo": "ぼ", + "po": "ぽ", + "ma": "ま", + "mi": "み", + "mu": "む", + "me": "め", + "mo": "も", + "ya": "や", + "xya": "ゃ", + "lya": "ゃ", + "yu": "ゆ", + "xyu": "ゅ", + "lyu": "ゅ", + "yo": "よ", + "xyo": "ょ", + "lyo": "ょ", + "ra": "ら", + "ri": "り", + "ru": "る", + "re": "れ", + "ro": "ろ", + "wa": "わ", + "xwa": "ゎ", + "lwa": "ゎ", + "wyi": "ゐ", + "wye": "ゑ", + "wo": "を", + "nn": "ん", + "ye": "いぇ", + "va": "ゔぁ", + "vi": "ゔぃ", + "ve": "ゔぇ", + "vo": "ゔぉ", + "kya": "きゃ", + "kyu": "きゅ", + "kye": "きぇ", + "kyo": "きょ", + "gya": "ぎゃ", + "gyu": "ぎゅ", + "gye": "ぎぇ", + "gyo": "ぎょ", + "qa": "くぁ", + "kwa": "くぁ", + "qwa": "くぁ", + "qi": "くぃ", + "kwi": "くぃ", + "qwi": "くぃ", + "qu": "くぅ", + "kwu": "くぅ", + "qwu": "くぅ", + "qe": "くぇ", + "kwe": "くぇ", + "qwe": "くぇ", + "qo": "くぉ", + "kwo": "くぉ", + "qwo": "くぉ", + "gwa": "ぐぁ", + "gwi": "ぐぃ", + "gwu": "ぐぅ", + "gwe": "ぐぇ", + "gwo": "ぐぉ", + "sha": "しゃ", + "sya": "しゃ", + "shu": "しゅ", + "syu": "しゅ", + "she": "しぇ", + "sye": "しぇ", + "sho": "しょ", + "syo": "しょ", + "ja": "じゃ", + "zya": "じゃ", + "jya": "じゃ", + "jyi": "じぃ", + "ju": "じゅ", + "zyu": "じゅ", + "jyu": "じゅ", + "je": "じぇ", + "zye": "じぇ", + "jye": "じぇ", + "jo": "じょ", + "zyo": "じょ", + "jyo": "じょ", + "swa": "すぁ", + "swi": "すぃ", + "swu": "すぅ", + "swe": "すぇ", + "swo": "すぉ", + "cha": "ちゃ", + "cya": "ちゃ", + "tya": "ちゃ", + "tyi": "ちぃ", + "cyi": "ちぃ", + "chu": "ちゅ", + "cyu": "ちゅ", + "tyu": "ちゅ", + "che": "ちぇ", + "cye": "ちぇ", + "tye": "ちぇ", + "cho": "ちょ", + "cyo": "ちょ", + "tyo": "ちょ", + "tsa": "つぁ", + "tsi": "つぃ", + "tse": "つぇ", + "tso": "つぉ", + "tha": "てゃ", + "thi": "てぃ", + "thu": "てゅ", + "the": "てぇ", + "tho": "てょ", + "twa": "とぁ", + "twi": "とぃ", + "twu": "とぅ", + "twe": "とぇ", + "two": "とぉ", + "dya": "ぢゃ", + "dyi": "ぢぃ", + "dyu": "ぢゅ", + "dye": "ぢぇ", + "dyo": "ぢょ", + "dha": "でゃ", + "dhi": "でぃ", + "dhu": "でゅ", + "dhe": "でぇ", + "dho": "でょ", + "dwa": "どぁ", + "dwi": "どぃ", + "dwu": "どぅ", + "dwe": "どぇ", + "dwo": "どぉ", + "nya": "にゃ", + "nyi": "にぃ", + "nyu": "にゅ", + "nye": "にぇ", + "nyo": "にょ", + "hya": "ひゃ", + "hyi": "ひぃ", + "hyu": "ひゅ", + "hye": "ひぇ", + "hyo": "ひょ", + "bya": "びゃ", + "byi": "びぃ", + "byu": "びゅ", + "bye": "びぇ", + "byo": "びょ", + "pya": "ぴゃ", + "pyi": "ぴぃ", + "pyu": "ぴゅ", + "pye": "ぴぇ", + "pyo": "ぴょ", + "fa": "ふぁ", + "hwa": "ふぁ", + "fwa": "ふぁ", + "fi": "ふぃ", + "hwi": "ふぃ", + "fwi": "ふぃ", + "fwu": "ふぅ", + "fe": "ふぇ", + "hwe": "ふぇ", + "fwe": "ふぇ", + "fo": "ふぉ", + "hwo": "ふぉ", + "fwo": "ふぉ", + "fya": "ふゃ", + "fyu": "ふゅ", + "fyo": "ふょ", + "mya": "みゃ", + "myi": "みぃ", + "myu": "みゅ", + "mye": "みぇ", + "myo": "みょ", + "rya": "りゃ", + "ryi": "りぃ", + "ryu": "りゅ", + "rye": "りぇ", + "ryo": "りょ", + "wi": "うぃ", + "we": "うぇ", + "wha": "うぁ", + "whi": "うぃ", + "whu": "う", + "whe": "うぇ", + "who": "うぉ", + "bb": "っb", + "cc": "っc", + "dd": "っd", + "ff": "っf", + "gg": "っg", + "hh": "っh", + "jj": "っj", + "kk": "っk", + "ll": "っl", + "mm": "っm", + "pp": "っp", + "qq": "っq", + "rr": "っr", + "ss": "っs", + "tt": "っt", + "vv": "っv", + "ww": "っw", + "xx": "っx", + "yy": "っy", + "zz": "っz", + "nb": "んb", + "nc": "んc", + "nd": "んd", + "nf": "んf", + "ng": "んg", + "nh": "んh", + "nj": "んj", + "nk": "んk", + "nl": "んl", + "nm": "んm", + "np": "んp", + "nq": "んq", + "nr": "んr", + "ns": "んs", + "nt": "んt", + "nv": "んv", + "nw": "んw", + "nx": "んx", + "nz": "んz", + "xn": "ん", + "zh": "←", + "zj": "↓", + "zk": "↑", + "zl": "→" + ].map {(Array($0.key), Array($0.value))}) + + static let defaultAzikMap: [[Character]: [Character]] = Dictionary(uniqueKeysWithValues: [ + "a": "あ", + "i": "い", + "u": "う", + "e": "え", + "o": "お", + "ka": "か", + "ki": "き", + "ku": "く", + "ke": "け", + "ko": "こ", + "sa": "さ", + "si": "し", + "su": "す", + "se": "せ", + "so": "そ", + "ta": "た", + "ti": "ち", + "tu": "つ", + "te": "て", + "to": "と", + "na": "な", + "ni": "に", + "nu": "ぬ", + "ne": "ね", + "no": "の", + "ha": "は", + "hi": "ひ", + "hu": "ふ", + "he": "へ", + "ho": "ほ", + "ma": "ま", + "mi": "み", + "mu": "む", + "me": "め", + "mo": "も", + "ya": "や", + "yu": "ゆ", + "yo": "よ", + "ra": "ら", + "ri": "り", + "ru": "る", + "re": "れ", + "ro": "ろ", + "wa": "わ", + "wi": "うぃ", + "we": "うぇ", + "wo": "を", + "ga": "が", + "gi": "ぎ", + "gu": "ぐ", + "ge": "げ", + "go": "ご", + "za": "ざ", + "zi": "じ", + "zu": "ず", + "ze": "ぜ", + "zo": "ぞ", + "da": "だ", + "di": "ぢ", + "du": "づ", + "de": "で", + "do": "ど", + "ba": "ば", + "bi": "び", + "bu": "ぶ", + "be": "べ", + "bo": "ぼ", + "pa": "ぱ", + "pi": "ぴ", + "pu": "ぷ", + "pe": "ぺ", + "po": "ぽ", + "kya": "きゃ", + "kyu": "きゅ", + "kye": "きぇ", + "kyo": "きょ", + "kga": "きゃ", + "kgu": "きゅ", + "kge": "きぇ", + "kgo": "きょ", + "sya": "しゃ", + "syu": "しゅ", + "sye": "しぇ", + "syo": "しょ", + "xa": "しゃ", + "xu": "しゅ", + "xe": "しぇ", + "xo": "しょ", + "tya": "ちゃ", + "tyu": "ちゅ", + "tye": "ちぇ", + "tyo": "ちょ", + "ca": "ちゃ", + "cu": "ちゅ", + "ce": "ちぇ", + "co": "ちょ", + "nya": "にゃ", + "nyu": "にゅ", + "nye": "にぇ", + "nyo": "にょ", + "nga": "にゃ", + "ngu": "にゅ", + "nge": "にぇ", + "ngo": "にょ", + "hya": "ひゃ", + "hyu": "ひゅ", + "hye": "ひぇ", + "hyo": "ひょ", + "hga": "ひゃ", + "hgu": "ひゅ", + "hge": "ひぇ", + "hgo": "ひょ", + "mya": "みゃ", + "myu": "みゅ", + "mye": "みぇ", + "myo": "みょ", + "mga": "みゃ", + "mgu": "みゅ", + "mge": "みぇ", + "mgo": "みょ", + "rya": "りゃ", + "ryu": "りゅ", + "rye": "りぇ", + "ryo": "りょ", + "gya": "ぎゃ", + "gyu": "ぎゅ", + "gye": "ぎぇ", + "gyo": "ぎょ", + "zya": "じゃ", + "zyu": "じゅ", + "zye": "じぇ", + "zyo": "じょ", + "ja": "じゃ", + "ju": "じゅ", + "je": "じぇ", + "jo": "じょ", + "bya": "びゃ", + "byu": "びゅ", + "bye": "びぇ", + "byo": "びょ", + "pya": "ぴゃ", + "pyu": "ぴゅ", + "pye": "ぴぇ", + "pyo": "ぴょ", + "pga": "ぴゃ", + "pgu": "ぴゅ", + "pge": "ぴぇ", + "pgo": "ぴょ", + "fa": "ふぁ", + "fi": "ふぃ", + "fu": "ふ", + "fe": "ふぇ", + "fo": "ふぉ", + "va": "ヴぁ", + "vi": "ヴぃ", + "vu": "ヴ", + "ve": "ヴぇ", + "vo": "ヴぉ", + "tgi": "てぃ", + "tgu": "とぅ", + "dci": "でぃ", + "dcu": "どぅ", + "wso": "うぉ", + "la": "ぁ", + "li": "ぃ", + "lu": "ぅ", + "le": "ぇ", + "lo": "ぉ", + "lya": "ゃ", + "lyu": "ゅ", + "lyo": "ょ", + ";": "っ", + "q": "ん", + "nn": "ん", + ":": "ー", + "z。": "…", + "z、": "‥", + "zー": "〜", + "z「": "『", + "z」": "』", + "kz": "かん", + "kn": "かん", + "kk": "きん", + "kj": "くん", + "kd": "けん", + "kl": "こん", + "sz": "さん", + "sn": "さん", + "sk": "しん", + "sj": "すん", + "sd": "せん", + "sl": "そん", + "tz": "たん", + "tn": "たん", + "tk": "ちん", + "tj": "つん", + "td": "てん", + "tl": "とん", + "nz": "なん", + "nk": "にん", + "nj": "ぬん", + "nd": "ねん", + "nl": "のん", + "hz": "はん", + "hn": "はん", + "hk": "ひん", + "hj": "ふん", + "hd": "へん", + "hl": "ほん", + "mz": "まん", + "mk": "みん", + "mj": "むん", + "md": "めん", + "ml": "もん", + "yz": "やん", + "yn": "やん", + "yj": "ゆん", + "yl": "よん", + "rz": "らん", + "rn": "らん", + "rk": "りん", + "rj": "るん", + "rd": "れん", + "rl": "ろん", + "wz": "わん", + "wn": "わん", + "wk": "うぃん", + "wd": "うぇん", + "wl": "うぉん", + "gz": "がん", + "gn": "がん", + "gk": "ぎん", + "gj": "ぐん", + "gd": "げん", + "gl": "ごん", + "zz": "ざん", + "zn": "ざん", + "zk": "じん", + "zj": "ずん", + "zd": "ぜん", + "zl": "ぞん", + "dz": "だん", + "dn": "だん", + "dk": "ぢん", + "dj": "づん", + "dd": "でん", + "dl": "どん", + "bz": "ばん", + "bn": "ばん", + "bk": "びん", + "bj": "ぶん", + "bd": "べん", + "bl": "ぼん", + "pz": "ぱん", + "pn": "ぱん", + "pk": "ぴん", + "pj": "ぷん", + "pd": "ぺん", + "pl": "ぽん", + "kyz": "きゃん", + "kyn": "きゃん", + "kyj": "きゅん", + "kyd": "きぇん", + "kyl": "きょん", + "kgz": "きゃん", + "kgn": "きゃん", + "kgj": "きゅん", + "kgd": "きぇん", + "kgl": "きょん", + "syz": "しゃん", + "syn": "しゃん", + "syj": "しゅん", + "syd": "しぇん", + "syl": "しょん", + "xz": "しゃん", + "xn": "しゃん", + "xj": "しゅん", + "xd": "しぇん", + "xl": "しょん", + "tyz": "ちゃん", + "tyn": "ちゃん", + "tyj": "ちゅん", + "tyd": "ちぇん", + "tyl": "ちょん", + "cz": "ちゃん", + "cn": "ちゃん", + "cj": "ちゅん", + "cd": "ちぇん", + "cl": "ちょん", + "nyz": "にゃん", + "nyn": "にゃん", + "nyj": "にゅん", + "nyd": "にぇん", + "nyl": "にょん", + "ngz": "にゃん", + "ngn": "にゃん", + "ngj": "にゅん", + "ngd": "にぇん", + "ngl": "にょん", + "hyz": "ひゃん", + "hyn": "ひゃん", + "hyj": "ひゅん", + "hyd": "ひぇん", + "hyl": "ひょん", + "hgz": "ひゃん", + "hgn": "ひゃん", + "hgj": "ひゅん", + "hgd": "ひぇん", + "hgl": "ひょん", + "myz": "みゃん", + "myn": "みゃん", + "myj": "みゅん", + "myd": "みぇん", + "myl": "みょん", + "mgz": "みゃん", + "mgn": "みゃん", + "mgj": "みゅん", + "mgd": "みぇん", + "mgl": "みょん", + "ryz": "りゃん", + "ryn": "りゃん", + "ryj": "りゅん", + "ryd": "りぇん", + "ryl": "りょん", + "gyz": "ぎゃん", + "gyn": "ぎゃん", + "gyj": "ぎゅん", + "gyd": "ぎぇん", + "gyl": "ぎょん", + "zyz": "じゃん", + "zyn": "じゃん", + "zyj": "じゅん", + "zyd": "じぇん", + "zyl": "じょん", + "jz": "じゃん", + "jn": "じゃん", + "jj": "じゅん", + "jd": "じぇん", + "jl": "じょん", + "byz": "びゃん", + "byn": "びゃん", + "byj": "びゅん", + "byd": "びぇん", + "byl": "びょん", + "pyz": "ぴゃん", + "pyn": "ぴゃん", + "pyj": "ぴゅん", + "pyd": "ぴぇん", + "pyl": "ぴょん", + "pgz": "ぴゃん", + "pgn": "ぴゃん", + "pgj": "ぴゅん", + "pgd": "ぴぇん", + "pgl": "ぴょん", + "fz": "ふぁん", + "fn": "ふぁん", + "fk": "ふぃん", + "fj": "ふん", + "fd": "ふぇん", + "fl": "ふぉん", + "vz": "ゔぁん", + "vn": "ゔぁん", + "vk": "ゔぃん", + "vj": "ゔん", + "vd": "ゔぇん", + "vl": "ゔぉん", + "tgk": "てぃん", + "tgj": "とぅん", + "dck": "でぃん", + "dcj": "どぅん", + "lz": "ぁん", + "ln": "ぁん", + "lk": "ぃん", + "ld": "ぇん", + "ll": "ぉん", + "lyz": "ゃん", + "lyn": "ゃん", + "lyj": "ゅん", + "lyl": "ょん", + "kq": "かい", + "kh": "くう", + "kw": "けい", + "kp": "こう", + "sq": "さい", + "sh": "すう", + "sw": "せい", + "sp": "そう", + "tq": "たい", + "th": "つう", + "tw": "てい", + "tp": "とう", + "nq": "ない", + "nh": "ぬう", + "nw": "ねい", + "np": "のう", + "hq": "はい", + "hh": "ふう", + "hw": "へい", + "hp": "ほう", + "mq": "まい", + "mh": "むう", + "mw": "めい", + "mp": "もう", + "yq": "やい", + "yh": "ゆう", + "yp": "よう", + "rq": "らい", + "rh": "るう", + "rw": "れい", + "rp": "ろう", + "gq": "がい", + "gh": "ぐう", + "gw": "げい", + "gp": "ごう", + "zq": "ざい", + "zh": "ずう", + "zw": "ぜい", + "zp": "ぞう", + "dq": "だい", + "dh": "づう", + "dw": "でい", + "dp": "どう", + "bq": "ばい", + "bh": "ぶう", + "bw": "べい", + "bp": "ぼう", + "pq": "ぱい", + "ph": "ぷう", + "pw": "ぺい", + "pp": "ぽう", + "kyq": "きゃい", + "kyh": "きゅう", + "kyw": "きぇい", + "kyp": "きょう", + "kgq": "きゃい", + "kgh": "きゅう", + "kgw": "きぇい", + "kgp": "きょう", + "syq": "しゃい", + "syh": "しゅう", + "syw": "しぇい", + "syp": "しょう", + "xq": "しゃい", + "xh": "しゅう", + "xw": "しぇい", + "xp": "しょう", + "tyq": "ちゃい", + "tyh": "ちゅう", + "tyw": "ちぇい", + "typ": "ちょう", + "cq": "ちゃい", + "ch": "ちゅう", + "cw": "ちぇい", + "cp": "ちょう", + "nyq": "にゃい", + "nyh": "にゅう", + "nyw": "にぇい", + "nyp": "にょう", + "ngq": "にゃい", + "ngh": "にゅう", + "ngw": "にぇい", + "ngp": "にょう", + "hyq": "ひゃい", + "hyh": "ひゅう", + "hyw": "ひぇい", + "hyp": "ひょう", + "hgq": "ひゃい", + "hgh": "ひゅう", + "hgw": "ひぇい", + "hgp": "ひょう", + "myq": "みゃい", + "myh": "みゅう", + "myw": "みぇい", + "myp": "みょう", + "mgq": "みゃい", + "mgh": "みゅう", + "mgw": "みぇい", + "mgp": "みょう", + "ryq": "りゃい", + "ryh": "りゅう", + "ryw": "りぇい", + "ryp": "りょう", + "gyq": "ぎゃい", + "gyh": "ぎゅう", + "gyw": "ぎぇい", + "gyp": "ぎょう", + "zyq": "じゃい", + "zyh": "じゅう", + "zyw": "じぇい", + "zyp": "じょう", + "jq": "じゃい", + "jh": "じゅう", + "jw": "じぇい", + "jp": "じょう", + "byq": "びゃい", + "byh": "びゅう", + "byw": "びぇい", + "byp": "びょう", + "pyq": "ぴゃい", + "pyh": "ぴゅう", + "pyw": "ぴぇい", + "pyp": "ぴょう", + "pgq": "ぴゃい", + "pgh": "ぴゅう", + "pgw": "ぴぇい", + "pgp": "ぴょう", + "fq": "ふぁい", + "fh": "ふう", + "fw": "ふぇい", + "fp": "ふぉー", + "vq": "ゔぁい", + "vh": "ゔー", + "vw": "ゔぇい", + "vp": "ゔぉー", + "tgh": "とぅー", + "dch": "どぅー", + "wq": "わい", + "ww": "うぇい", + "wp": "うぉー", + "lq": "ぁい", + "lh": "ぅう", + "lw": "ぇい", + "lp": "ぉう", + "lyq": "ゃい", + "lyh": "ゅう", + "lyp": "ょう", + "kf": "き", + "jf": "じゅ", + "hf": "ふ", + "yf": "ゆ", + "mf": "む", + "nf": "ぬ", + "df": "で", + "cf": "ちぇ", + "pf": "ぽん", + "wf": "わい", + "sf": "さい", + "ss": "せい", + "zc": "ざ", + "zv": "ざい", + "zf": "ぜ", + "zx": "ぜい", + "kt": "こと", + "wt": "わた", + "km": "かも", + "sr": "する", + "rr": "られ", + "nb": "ねば", + "nt": "にち", + "st": "した", + "mn": "もの", + "tm": "ため", + "tr": "たら", + "zr": "ざる", + "bt": "びと", + "dt": "だち", + "tt": "たち", + "ms": "ます", + "dm": "でも", + "nr": "なる", + "mt": "また", + "gr": "がら", + "wr": "われ", + "ht": "ひと", + "ds": "です", + "kr": "から", + "yr": "よる", + "tb": "たび", + "gt": "ごと", + ].map {(Array($0.key), Array($0.value))}) +} diff --git a/Sources/KanaKanjiConverterModule/InputManagement/States.swift b/Sources/KanaKanjiConverterModule/InputManagement/States.swift index fe79602..5395632 100644 --- a/Sources/KanaKanjiConverterModule/InputManagement/States.swift +++ b/Sources/KanaKanjiConverterModule/InputManagement/States.swift @@ -5,13 +5,6 @@ // Created by ensan on 2023/04/30. // -public enum InputStyle: String, Sendable { - /// 入力された文字を直接入力するスタイル - case direct = "direct" - /// ローマ字日本語入力とするスタイル - case roman2kana = "roman" -} - public enum KeyboardLanguage: String, Codable, Equatable, Sendable { case en_US case ja_JP diff --git a/Sources/SwiftUtils/CharacterUtils.swift b/Sources/SwiftUtils/CharacterUtils.swift index db6f423..38def1a 100644 --- a/Sources/SwiftUtils/CharacterUtils.swift +++ b/Sources/SwiftUtils/CharacterUtils.swift @@ -25,11 +25,6 @@ public enum CharacterUtils { kogakiKana.contains(character) } - /// ローマ字(a-z, A-Zか否か) - @inlinable public static func isRomanLetter(_ character: Character) -> Bool { - character.isASCII && character.isCased - } - /// 自分が小書きであれば該当する文字を返す。 public static func kogaki(_ character: Character) -> Character { switch character { diff --git a/Tests/KanaKanjiConverterModuleTests/ComposingTextTests.swift b/Tests/KanaKanjiConverterModuleTests/ComposingTextTests.swift index e992de1..146e633 100644 --- a/Tests/KanaKanjiConverterModuleTests/ComposingTextTests.swift +++ b/Tests/KanaKanjiConverterModuleTests/ComposingTextTests.swift @@ -140,7 +140,7 @@ final class ComposingTextTests: XCTestCase { ComposingText.InputElement(character: "a", inputStyle: .roman2kana), ComposingText.InputElement(character: "k", inputStyle: .roman2kana), ComposingText.InputElement(character: "a", inputStyle: .roman2kana), - ComposingText.InputElement(character: "ふ", inputStyle: .direct) + ComposingText.InputElement(character: "ふ", inputStyle: .frozen) ]) XCTAssertEqual(c.convertTarget, "あかふ") XCTAssertEqual(c.convertTargetCursorPosition, 3) diff --git a/Tests/KanaKanjiConverterModuleTests/InputStyleManagerTests.swift b/Tests/KanaKanjiConverterModuleTests/InputStyleManagerTests.swift new file mode 100644 index 0000000..944695a --- /dev/null +++ b/Tests/KanaKanjiConverterModuleTests/InputStyleManagerTests.swift @@ -0,0 +1,28 @@ +@testable import KanaKanjiConverterModule +import XCTest + +final class InputStyleManagerTests: XCTestCase { + func testCustomTableLoading() throws { + let url = FileManager.default.temporaryDirectory.appendingPathComponent("custom.tsv") + try "a\tあ\nka\tか\n".write(to: url, atomically: true, encoding: .utf8) + let table = InputStyleManager.shared.table(for: .custom(url)) + XCTAssertEqual(table.toHiragana(currentText: [], added: "a"), Array("あ")) + XCTAssertEqual(table.toHiragana(currentText: ["k"], added: "a"), Array("か")) + } + + func testCustomTableLoadingWithBlankLines() throws { + let url = FileManager.default.temporaryDirectory.appendingPathComponent("custom.tsv") + try "a\tあ\n\n\nka\tか\n".write(to: url, atomically: true, encoding: .utf8) + let table = InputStyleManager.shared.table(for: .custom(url)) + XCTAssertEqual(table.toHiragana(currentText: [], added: "a"), Array("あ")) + XCTAssertEqual(table.toHiragana(currentText: ["k"], added: "a"), Array("か")) + } + + func testCustomTableLoadingWithCommentLines() throws { + let url = FileManager.default.temporaryDirectory.appendingPathComponent("custom.tsv") + try "a\tあ\n# here is comment\nka\tか\n".write(to: url, atomically: true, encoding: .utf8) + let table = InputStyleManager.shared.table(for: .custom(url)) + XCTAssertEqual(table.toHiragana(currentText: [], added: "a"), Array("あ")) + XCTAssertEqual(table.toHiragana(currentText: ["k"], added: "a"), Array("か")) + } +} diff --git a/Tests/KanaKanjiConverterModuleTests/Roman2KanaTests.swift b/Tests/KanaKanjiConverterModuleTests/Roman2KanaTests.swift index 0e24e54..9673111 100644 --- a/Tests/KanaKanjiConverterModuleTests/Roman2KanaTests.swift +++ b/Tests/KanaKanjiConverterModuleTests/Roman2KanaTests.swift @@ -3,24 +3,25 @@ import XCTest final class Roman2KanaTests: XCTestCase { func testToHiragana() throws { + let table = InputStyleManager.shared.table(for: .defaultRomanToKana) // xtsu -> っ - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array(""), added: "x"), Array("x")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("x"), added: "t"), Array("xt")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("xt"), added: "s"), Array("xts")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("xts"), added: "u"), Array("っ")) + XCTAssertEqual(table.toHiragana(currentText: Array(""), added: "x"), Array("x")) + XCTAssertEqual(table.toHiragana(currentText: Array("x"), added: "t"), Array("xt")) + XCTAssertEqual(table.toHiragana(currentText: Array("xt"), added: "s"), Array("xts")) + XCTAssertEqual(table.toHiragana(currentText: Array("xts"), added: "u"), Array("っ")) // kanto -> かんと - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array(""), added: "k"), Array("k")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("k"), added: "a"), Array("か")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("か"), added: "n"), Array("かn")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("かn"), added: "t"), Array("かんt")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("かんt"), added: "o"), Array("かんと")) + XCTAssertEqual(table.toHiragana(currentText: Array(""), added: "k"), Array("k")) + XCTAssertEqual(table.toHiragana(currentText: Array("k"), added: "a"), Array("か")) + XCTAssertEqual(table.toHiragana(currentText: Array("か"), added: "n"), Array("かn")) + XCTAssertEqual(table.toHiragana(currentText: Array("かn"), added: "t"), Array("かんt")) + XCTAssertEqual(table.toHiragana(currentText: Array("かんt"), added: "o"), Array("かんと")) // zl -> → - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array(""), added: "z"), Array("z")) - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("z"), added: "l"), Array("→")) + XCTAssertEqual(table.toHiragana(currentText: Array(""), added: "z"), Array("z")) + XCTAssertEqual(table.toHiragana(currentText: Array("z"), added: "l"), Array("→")) // TT -> TT - XCTAssertEqual(Roman2Kana.toHiragana(currentText: Array("T"), added: "T"), Array("TT")) + XCTAssertEqual(table.toHiragana(currentText: Array("T"), added: "T"), Array("TT")) } } diff --git a/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift b/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift index 81adf17..6578d00 100644 --- a/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift +++ b/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/ConverterTests/ConverterTests.swift @@ -75,6 +75,29 @@ final class ConverterTests: XCTestCase { } } + func testAzikFullConversion() async throws { + for needTypoCorrection in [true, false] { + do { + let converter = await KanaKanjiConverter() + var c = ComposingText() + // : -> ー, sk -> しん, dq → だい, kf -> き, ds: です + c.insertAtCursorPosition("azu:ki:haskzidqnokf:bo:doapurids", inputStyle: .mapped(id: .defaultAZIK)) + XCTAssertEqual(c.convertTarget, "あずーきーはしんじだいのきーぼーどあぷりです") + let results = await converter.requestCandidates(c, options: requestOptions(needTypoCorrection: needTypoCorrection)) + XCTAssertEqual(results.mainResults.first?.text, "azooKeyは新時代のキーボードアプリです") + } + do { + let converter = await KanaKanjiConverter() + var c = ComposingText() + // yp -> よう, xp -> しょう, kf -> き, kr -> から, kyh -> きゅう, rk -> りん, kd -> けん, pp -> ぽう, : -> ー, kw -> けい, gr -> がら, ; -> っ, kp -> こう, dq -> だい, sz -> さん, kk -> きん, tq -> たい, zq -> ざい, tw -> てい + c.insertAtCursorPosition("ypxpkfkrtenisusuieiyakyhxprkzikdppnadosamazamanasupo:tuwokwkdsinagrsodatixpga;kpzidqharoszzerusukkkpnitqzqsiteorigoruhuyatenisuwonara;twta", inputStyle: .mapped(id: .defaultAZIK)) + XCTAssertEqual(c.convertTarget, "ようしょうきからてにすすいえいやきゅうしょうりんじけんぽうなどさまざまなすぽーつをけいけんしながらそだちしょうがっこうじだいはろさんぜるすきんこうにたいざいしておりごるふやてにすをならっていた") + let results = await converter.requestCandidates(c, options: requestOptions(needTypoCorrection: needTypoCorrection)) + XCTAssertEqual(results.mainResults.first?.text, "幼少期からテニス水泳野球少林寺拳法など様々なスポーツを経験しながら育ち小学校時代はロサンゼルス近郊に滞在しておりゴルフやテニスを習っていた") + } + } + } + // 1文字ずつ変換する // memo: 内部実装としては別のモジュールが呼ばれるのだが、それをテストする方法があまりないかもしれない func testGradualConversion() async throws { diff --git a/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/DicdataStoreTests/DicdataStoreTests.swift b/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/DicdataStoreTests/DicdataStoreTests.swift index 84f3dc1..c31a5cd 100644 --- a/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/DicdataStoreTests/DicdataStoreTests.swift +++ b/Tests/KanaKanjiConverterModuleWithDefaultDictionaryTests/DicdataStoreTests/DicdataStoreTests.swift @@ -331,7 +331,7 @@ final class DicdataStoreTests: XCTestCase { } func testPossibleNexts() throws { - let possibleNexts = DicdataStore.possibleNexts + let possibleNexts = InputStyleManager.shared.table(for: .defaultRomanToKana).possibleNexts XCTAssertEqual(Set(possibleNexts["f", default: []]).symmetricDifference(["ファ", "フィ", "フ", "フェ", "フォ", "フャ", "フュ", "フョ", "フゥ", "ッf"]), []) XCTAssertEqual(Set(possibleNexts["xy", default: []]).symmetricDifference(["ャ", "ョ", "ュ"]), []) XCTAssertEqual(possibleNexts["", default: []], []) diff --git a/Tests/SwiftUtilsTests/CharacterUtilsTests.swift b/Tests/SwiftUtilsTests/CharacterUtilsTests.swift index 72d099d..770b70c 100644 --- a/Tests/SwiftUtilsTests/CharacterUtilsTests.swift +++ b/Tests/SwiftUtilsTests/CharacterUtilsTests.swift @@ -24,18 +24,6 @@ final class CharacterUtilsTests: XCTestCase { XCTAssertFalse(CharacterUtils.isKogana("!")) } - func testIsRomanLetter() throws { - XCTAssertTrue(CharacterUtils.isRomanLetter("a")) - XCTAssertTrue(CharacterUtils.isRomanLetter("A")) - XCTAssertTrue(CharacterUtils.isRomanLetter("b")) - - XCTAssertFalse(CharacterUtils.isRomanLetter("ぁ")) - XCTAssertFalse(CharacterUtils.isRomanLetter("'")) - XCTAssertFalse(CharacterUtils.isRomanLetter("あ")) - XCTAssertFalse(CharacterUtils.isRomanLetter("カ")) - XCTAssertFalse(CharacterUtils.isRomanLetter("!")) - } - func testIsDakuten() throws { XCTAssertTrue(CharacterUtils.isDakuten("が")) XCTAssertTrue(CharacterUtils.isDakuten("ば"))