Add comma separated number special candidate

This commit is contained in:
Miwa
2025-05-25 18:40:18 +09:00
parent 987baa30c3
commit 5e58458895
5 changed files with 101 additions and 1 deletions

View File

@ -0,0 +1,46 @@
import Foundation
extension KanaKanjiConverter {
func commaSeparatedNumberCandidates(_ inputData: ComposingText) -> [Candidate] {
var text = inputData.convertTarget
guard !text.isEmpty else { return [] }
var negative = false
if text.first == "-" {
negative = true
text.removeFirst()
}
let parts = text.split(separator: ".", omittingEmptySubsequences: false)
guard parts.count <= 2,
parts.allSatisfy({ !$0.isEmpty && $0.allSatisfy({ $0.isNumber && $0.isASCII }) }) else {
return []
}
let integerPart = parts[0]
guard integerPart.count > 3 else { return [] }
var reversed = Array(integerPart.reversed())
var formatted = ""
for (i, ch) in reversed.enumerated() {
if i > 0 && i % 3 == 0 {
formatted.append(",")
}
formatted.append(ch)
}
let integerString = String(formatted.reversed())
var result = (negative ? "-" : "") + integerString
if parts.count == 2 {
let fractional = parts[1]
result += "." + fractional
}
let ruby = inputData.convertTarget.toKatakana()
let candidate = Candidate(
text: result,
value: -10,
correspondingCount: inputData.input.count,
lastMid: MIDData..mid,
data: [DicdataElement(word: result, ruby: ruby, cid: CIDData..cid, mid: MIDData..mid, value: -10)]
)
return [candidate]
}
}

View File

@ -83,6 +83,7 @@ public struct ConvertRequestOptions: Sendable {
specialCandidateProviders.append(.timeExpression)
specialCandidateProviders.append(.calendar)
specialCandidateProviders.append(.version)
specialCandidateProviders.append(.commaSeparatedNumber)
self.N_best = N_best
self.requireJapanesePrediction = requireJapanesePrediction

View File

@ -23,7 +23,8 @@ import EfficientNGram
EmailAddressSpecialCandidateProvider(),
UnicodeSpecialCandidateProvider(),
VersionSpecialCandidateProvider(),
TimeExpressionSpecialCandidateProvider()
TimeExpressionSpecialCandidateProvider(),
CommaSeparatedNumberSpecialCandidateProvider()
]
@MainActor private var checker = SpellChecker()
private var checkerInitialized: [KeyboardLanguage: Bool] = [.none: true, .ja_JP: true]

View File

@ -45,6 +45,13 @@ public struct TimeExpressionSpecialCandidateProvider: SpecialCandidateProvider {
}
}
public struct CommaSeparatedNumberSpecialCandidateProvider: SpecialCandidateProvider {
public init() {}
@MainActor public func provideCandidates(converter: KanaKanjiConverter, inputData: ComposingText, options _: ConvertRequestOptions) -> [Candidate] {
converter.commaSeparatedNumberCandidates(inputData)
}
}
public extension SpecialCandidateProvider where Self == CalendarSpecialCandidateProvider {
static var calendar: Self { .init() }
}
@ -68,3 +75,7 @@ public extension SpecialCandidateProvider where Self == VersionSpecialCandidateP
public extension SpecialCandidateProvider where Self == TimeExpressionSpecialCandidateProvider {
static var timeExpression: Self { .init() }
}
public extension SpecialCandidateProvider where Self == CommaSeparatedNumberSpecialCandidateProvider {
static var commaSeparatedNumber: Self { .init() }
}

View File

@ -0,0 +1,41 @@
import XCTest
@testable import KanaKanjiConverterModule
final class CommaSeparatedNumberTests: XCTestCase {
private func makeDirectInput(direct input: String) -> ComposingText {
ComposingText(
convertTargetCursorPosition: input.count,
input: input.map { .init(character: $0, inputStyle: .direct) },
convertTarget: input
)
}
func testCommaSeparatedNumberCandidates() async throws {
let converter = await KanaKanjiConverter()
func result(_ text: String) async -> [Candidate] {
await converter.commaSeparatedNumberCandidates(makeDirectInput(direct: text))
}
let r1 = await result("49000")
XCTAssertEqual(r1.first?.text, "49,000")
let r2 = await result("109428081")
XCTAssertEqual(r2.first?.text, "109,428,081")
let r3 = await result("2129.49")
XCTAssertEqual(r3.first?.text, "2,129.49")
let r4 = await result("-13932")
XCTAssertEqual(r4.first?.text, "-13,932")
let r5 = await result("12")
XCTAssertTrue(r5.isEmpty)
let r6 = await result("1A9B")
XCTAssertTrue(r6.isEmpty)
let r7 = await result("")
XCTAssertTrue(r7.isEmpty)
}
}