Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ public enum LabMarkerCsvImport {
public static func customKey(_ name: String) -> String {
let lowered = Array(name.precomposedStringWithCanonicalMapping
.trimmingCharacters(in: .whitespaces).lowercased())
// Kotlin twin: `LabMarkerCsvImport.isWordChar`.
func isWordChar(_ ch: Character) -> Bool { ch.isLetter || ch.isNumber }
var mapped = ""
for (i, ch) in lowered.enumerated() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,13 @@ final class LabMarkerCsvImportTests: XCTestCase {
}

/// Pinned by oracle: the same literals are asserted by the Android twin
/// (LabMarkerCsvImportTest.customKeyIsPinned). Names without "%" keep their pre-"pct" keys.
/// (LabMarkerCsvImportTest.customKeyIsPinned). The Unicode numeric cases pin #2351 parity.
func testCustomKeyIsPinned() {
let names = [
"LYMPH", "LYMPH %", "LYMPH%", "Lymph (%)", "BASO", "BASO %", "% Neutrophils", "NEUT %", "a%%b", "%", "Apo B", "Magnesium", " ??? ", "A - B", "Vitamin D (25-OH)", "Caf\u{e9} Marker", "Cafe\u{301} Marker", "MCHC g/dL", "HbA1c (IFCC)",
"LYMPH", "LYMPH %", "LYMPH%", "Lymph (%)", "BASO", "BASO %", "% Neutrophils", "NEUT %", "a%%b", "%", "Apo B", "Magnesium", " ??? ", "A - B", "Vitamin D (25-OH)", "Caf\u{e9} Marker", "Cafe\u{301} Marker", "MCHC g/dL", "HbA1c (IFCC)", "eGFR mL/min/1.73m²", "Factor Ⅻ", "Vitamin B₁₂", "①%", "½ dose", "³", "2", "𐄇",
]
let expected = [
"custom_lymph", "custom_lymph_pct", "custom_lymph_pct", "custom_lymph_pct", "custom_baso", "custom_baso_pct", "custom_pct_neutrophils", "custom_neut_pct", "custom_a_pctpct_b", "custom_pct", "custom_apo_b", "custom_magnesium", "", "custom_a__b", "custom_vitamin_d_25_oh", "custom_café_marker", "custom_café_marker", "custom_mchc_g_dl", "custom_hba1c_ifcc",
"custom_lymph", "custom_lymph_pct", "custom_lymph_pct", "custom_lymph_pct", "custom_baso", "custom_baso_pct", "custom_pct_neutrophils", "custom_neut_pct", "custom_a_pctpct_b", "custom_pct", "custom_apo_b", "custom_magnesium", "", "custom_a__b", "custom_vitamin_d_25_oh", "custom_café_marker", "custom_café_marker", "custom_mchc_g_dl", "custom_hba1c_ifcc", "custom_egfr_ml_min_1_73m²", "custom_factor_ⅻ", "custom_vitamin_b₁₂", "custom_①_pct", "custom_½_dose", "custom_³", "custom_2", "custom_𐄇",
]
XCTAssertEqual(names.map(LabMarkerCsvImport.customKey), expected)
}
Expand Down
10 changes: 5 additions & 5 deletions Tools/parity_twin_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
},
"authority": {
"files": {"count": 507, "sha256": "efc6950759de76273adccde8b6457f5e8577bdaf6ed9c6538cde79b0eaf0c612"},
"functions": {"count": 4517, "sha256": "05dc56778990d1a6313fc8429b783110ef534ebe49693d91ab7da9c736df040c"},
"functions": {"count": 4518, "sha256": "f424a4e32aceb96b06b2dc790e858bb55b2548fb326c1be1cfb85c6308d013aa"},
"properties": {"count": 488, "sha256": "77d60fe0f6218252233b9e60da8bf5135968e49be3c6d2027d0973ea20ddf792"},
"constants": {"count": 1985, "sha256": "0c67e7c378db2c0518339f073b37967e30cec0d5b586c1408841ca5e7977f9b4"},
"file_pairs": {"count": 78, "sha256": "ec0d88b4e0535c3b1051b5c10205f21174944018669fbe3479a3c21801a6dc38"},
"function_pairs": {"count": 215, "sha256": "f4ffaafbdf720d27594002cc2337a7b1be5635ec8d02720b7722f01656e8720b"},
"file_pairs": {"count": 79, "sha256": "02ee127080cf87c98925a60b30b6e87d25e7a82ef6cf43b6251fe24c20600dd1"},
"function_pairs": {"count": 217, "sha256": "c31664b757a428dcadbf56068d92c681397dc2f378e5e6d6ea65be6e70c3a381"},
"property_pairs": {"count": 163, "sha256": "b7d4a29086beec661993370657c0bcdae86f79b221fdc0043206161df5e1117f"},
"constant_pairs": {"count": 694, "sha256": "ae11bdde04d49f9dfe83170f0aa362e6bf18d3fdc381c1bdf5bbc256caa400ab"},
"unpaired_files": {"count": 373, "sha256": "2b0c9bc717b301037f735ea7cc32f7a7cd9b4a7887d46550b9e727ffd66d3639"},
"unpaired_functions": {"count": 4100, "sha256": "cc10b9887488dddae0c270071d3fde36a0c6c764df318e18a27e569e181051a9"},
"unpaired_files": {"count": 371, "sha256": "d9e682eadcdd5b1279a69a45799fd68b4589f98c83cfb7e15d3016ad890a98c0"},
"unpaired_functions": {"count": 4097, "sha256": "e54745434cdd990e42445672ec7ae137bfe1111def4f701948fa1ae7398e5871"},
"unpaired_properties": {"count": 162, "sha256": "dabff97ba9bce0aebdc8ef84b9c4f107ce574020e8b0455d8f9214082948246b"},
"unpaired_constants": {"count": 597, "sha256": "578c607cc05b1cd1f867c206c9fab70237ac40707cd49d4e0c5cc66b400184c7"}
}
Expand Down
22 changes: 15 additions & 7 deletions android/app/src/main/java/com/noop/ingest/LabMarkerCsvImport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -453,19 +453,27 @@ object LabMarkerCsvImport {
* other punctuation gave both `custom_lymph`, so the second row of the day overwrote the
* first. The token is joined to a letter/digit neighbour by one "_" (a non-alphanumeric
* neighbour already maps to "_"), so "LYMPH %", "LYMPH%" and "Lymph (%)" all give
* `custom_lymph_pct`. Names without "%" keep the keys they always had. Byte-identical to
* Swift `LabMarkerCsvImport.customKey`.
* `custom_lymph_pct`. Names without "%" keep their keys except those containing Unicode
* number letters or other numbers, which Android previously replaced with underscores (#2351).
* Swift twin: `LabMarkerCsvImport.customKey`. NFC-composable names produce the same key;
* Swift's grapheme clusters and Kotlin's code points can differ for uncomposed marks.
*/
internal fun customKey(name: String): String {
val lowered = Normalizer.normalize(name, Normalizer.Form.NFC).trim().lowercase()
// Swift Character.isNumber also keeps Unicode letter/other numbers (e.g. Ⅻ, ²).
// Swift twin: `LabMarkerCsvImport.isWordChar`.
fun isWordChar(cp: Int): Boolean = Character.isLetterOrDigit(cp) ||
Character.getType(cp) == Character.LETTER_NUMBER.toInt() ||
Character.getType(cp) == Character.OTHER_NUMBER.toInt()
val chars = lowered.codePoints().toArray()
val mapped = buildString {
lowered.forEachIndexed { i, ch ->
if (ch == '%') {
if (i > 0 && lowered[i - 1].isLetterOrDigit()) append('_')
chars.forEachIndexed { i, ch ->
if (ch == '%'.code) {
if (i > 0 && isWordChar(chars[i - 1])) append('_')
append("pct")
if (i + 1 < lowered.length && lowered[i + 1].isLetterOrDigit()) append('_')
if (i + 1 < chars.size && isWordChar(chars[i + 1])) append('_')
} else {
append(if (ch.isLetterOrDigit()) ch else '_')
if (isWordChar(ch)) appendCodePoint(ch) else append('_')
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,15 @@ class LabMarkerCsvImportTest {

/**
* Expected literals are the verbatim stdout of Swift `LabMarkerCsvImport.customKey` compiled
* standalone over [names] (the Swift suite pins the same list). Names without "%" keep their
* pre-"pct" keys.
* standalone over [names] (the Swift suite pins the same list). The Unicode numeric cases
* guard the Android/Swift category boundary from #2351.
*/
@Test fun customKeyIsPinned() {
val names = listOf(
"LYMPH", "LYMPH %", "LYMPH%", "Lymph (%)", "BASO", "BASO %", "% Neutrophils", "NEUT %", "a%%b", "%", "Apo B", "Magnesium", " ??? ", "A - B", "Vitamin D (25-OH)", "Caf\u00e9 Marker", "Cafe\u0301 Marker", "MCHC g/dL", "HbA1c (IFCC)",
"LYMPH", "LYMPH %", "LYMPH%", "Lymph (%)", "BASO", "BASO %", "% Neutrophils", "NEUT %", "a%%b", "%", "Apo B", "Magnesium", " ??? ", "A - B", "Vitamin D (25-OH)", "Caf\u00e9 Marker", "Cafe\u0301 Marker", "MCHC g/dL", "HbA1c (IFCC)", "eGFR mL/min/1.73m²", "Factor Ⅻ", "Vitamin B₁₂", "①%", "½ dose", "³", "2", "𐄇",
)
val expected = listOf(
"custom_lymph", "custom_lymph_pct", "custom_lymph_pct", "custom_lymph_pct", "custom_baso", "custom_baso_pct", "custom_pct_neutrophils", "custom_neut_pct", "custom_a_pctpct_b", "custom_pct", "custom_apo_b", "custom_magnesium", "", "custom_a__b", "custom_vitamin_d_25_oh", "custom_café_marker", "custom_café_marker", "custom_mchc_g_dl", "custom_hba1c_ifcc",
"custom_lymph", "custom_lymph_pct", "custom_lymph_pct", "custom_lymph_pct", "custom_baso", "custom_baso_pct", "custom_pct_neutrophils", "custom_neut_pct", "custom_a_pctpct_b", "custom_pct", "custom_apo_b", "custom_magnesium", "", "custom_a__b", "custom_vitamin_d_25_oh", "custom_café_marker", "custom_café_marker", "custom_mchc_g_dl", "custom_hba1c_ifcc", "custom_egfr_ml_min_1_73m²", "custom_factor_ⅻ", "custom_vitamin_b₁₂", "custom_①_pct", "custom_½_dose", "custom_³", "custom_2", "custom_𐄇",
)
assertEquals(expected, names.map { LabMarkerCsvImport.customKey(it) })
}
Expand Down
Loading