Skip to contents

cjk_normalize() puts text into one of the Unicode normalisation forms, so that strings which look the same compare the same. It optionally removes variation selectors first, which defeat an exact match on their own and which only one of the forms takes out.

Usage

cjk_normalize(
  x,
  form = c("nfc", "nfd", "nfkc", "nfkd", "nfkc_casefold"),
  drop_variation_selectors = FALSE
)

Arguments

x

A character vector. Anything else is coerced with as.character(). That coercion is R's, not this package's, so a numeric vector is measured as R chooses to write it – which moves with options(scipen) and options(OutDec), and can therefore differ between sessions. Convert deliberately if you mean to measure numbers; these verbs are for text. A list is not coerced – as.character() deparses one rather than coercing it, so the text measured would be the R code that builds the list – so a list, a data frame or a function is an error naming what to do instead.

form

One of "nfc", "nfd", "nfkc", "nfkd" or "nfkc_casefold". Defaults to "nfc".

drop_variation_selectors

Remove variation selectors before normalising. Defaults to FALSE.

Value

A character vector the same length as x. NA input gives NA; the empty string gives the empty string.

Details

Two strings can render identically and still differ as data. Japanese U+304C is one code point or two depending on where the text came from; a compatibility ideograph and its unified equivalent are distinct code points for the same character; an ideographic variation sequence adds an invisible selector that only a font reacts to. Any of these will split one token into two in a frequency count, a join or a group_by().

Which form

"nfc" and "nfd" are canonical: they preserve the identity of every character and differ only in whether marks are composed. "nfc" is the form almost all text is already in and the right default.

"nfkc" and "nfkd" add compatibility folding, which is lossy by design. On CJK text it narrows fullwidth ASCII, widens halfwidth katakana, turns the ideographic space U+3000 into a plain space, and unpacks circled numbers and squared kana – U+3314 becomes the two characters U+30AD U+30ED. That is the right trade for search and token matching and the wrong one for anything that has to be shown back to a reader. "nfkc_casefold" is "nfkc" plus case folding, for case-insensitive matching of the Latin mixed into CJK text.

"nfkc_casefold" also deletes every code point Unicode marks Default_Ignorable, which the other four forms all keep. That is the variation selectors below, and with them the zero-width joiner and non-joiner, the zero-width space, the soft hyphen, the tag characters and U+FEFF – so a byte-order mark at the front of a string survives the other forms and does not survive this one. For matching that is the point. If you need the folding without the deletions, use "nfkc" and lowercase separately.

to_halfwidth() and to_fullwidth() are the narrow alternative: they move text along the width axis and touch nothing else.

NFC is not a no-op on CJK

Canonical normalisation is usually described as safe, and on Latin text it is. On Han it is not. Compatibility ideographs have singleton canonical mappings, so "nfc" rewrites them just as "nfkc" does: U+FA0C becomes U+5140 and U+F900 becomes U+8C48. Of the 472 characters in the CJK Compatibility Ideographs block, 460 are folded away by plain NFC; the 12 that survive – U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29 – are unified ideographs that were encoded in that block by accident and have no mapping to apply. All 542 characters of the Supplement block are folded. So the block is not preserved and not uniformly folded either: if the distinction between a compatibility ideograph and its unified form carries meaning in your data, as it can in Korean and Japanese name records, normalising at all will destroy most of it. Keep the original column.

Variation selectors

drop_variation_selectors = TRUE removes them before the form is applied. They are removed first rather than afterwards because a selector blocks canonical composition across itself, so stripping one after normalising can leave text that is no longer in the form just requested.

The set removed is Unicode's Variation_Selector property, not a list written out here: the sixteen at U+FE00..U+FE0F, the 240 ideographic ones at U+E0100..U+E01EF, and the Mongolian free variation selectors – of which there are four on a current ICU and three on one built against Unicode 10.0, because U+180F arrived in Unicode 14.0. Taking the set from the property rather than from a literal means the verb follows whichever ICU is installed. Dropping them merges the glyph variants of a character into one token, which is what a frequency count wants and what a typesetter does not.

"nfc", "nfd", "nfkc" and "nfkd" all leave every one of them in place, so this argument is the only way to be rid of them under those forms. "nfkc_casefold" removes them regardless, as part of the wider deletion described above, and the argument is then redundant rather than wrong.

See also

to_halfwidth() and to_fullwidth() for the width axis alone, cjk_sort() for comparison that ignores these differences without rewriting the text.

Examples

# the same syllable composed and decomposed, which nchar() can tell apart
both <- c("\u304c", "\u304b\u3099")
nchar(both)
#> [1] 1 2
nchar(cjk_normalize(both))
#> [1] 1 1

# NFKC folds the width axis and more besides; NFC leaves both alone
cjk_normalize("\uff21\uff22\u3000\u2460", form = "nfkc")
#> [1] "AB 1"
cjk_normalize("\uff21\uff22\u3000\u2460", form = "nfc")
#> [1] "AB ①"

# a compatibility ideograph is rewritten by plain NFC
sprintf("%X", utf8ToInt(cjk_normalize("\ufa0c")))
#> [1] "5140"

# an ideographic variation sequence survives every form until dropped
ivs <- "\u8fbb\U000E0100"
nchar(cjk_normalize(ivs))
#> [1] 2
nchar(cjk_normalize(ivs, drop_variation_selectors = TRUE))
#> [1] 1