Swift · Case Folding and Normalization

Case Folding and Normalization

import StringZilla 6.0.0-rc.1 · 91a27bdd

utf8UncasedFind(substring:) performs a case-insensitive search using full Unicode folding and returns a byte-accurate Range<Index>?. The matched length can differ from the needle length, since folding can change byte counts.

if let range = "Grüße".utf8UncasedFind(substring: "GRÜSSE") {
    print("matched", "Grüße"[range])
}

For repeated case-insensitive searches with the same needle, build a Utf8UncasedNeedle once and reuse it. The needle is prepared once and never changes, so one needle can search from many threads at once. An empty needle matches at the start of any haystack with an empty range.

let needle = Utf8UncasedNeedle("hello")
let r = needle.findFirst(in: "Say HELLO to the world")
assert(r != nil)

utf8UncasedFoldedBytes() returns the fully case-folded UTF-8 bytes, which may be longer than the input since "ß" folds to "ss".

let folded = "Straße".utf8UncasedFoldedBytes()  // [UInt8] of "strasse"
assert(folded == Array("strasse".utf8))

Normalization is driven by StringZillaNormalizationForm, one of .nfc, .nfd, .nfkc, or .nfkd. utf8Normalized(_:) returns the normalized UTF-8 bytes and defaults to .nfc, utf8NormalizationViolation(_:) returns the Index? of the first non-conforming byte, and isUtf8Normalized(_:) is a convenience Bool.

let nfc = "e\u{0301}".utf8Normalized(.nfc)  // composed "é" bytes
assert("café".isUtf8Normalized(.nfc))       // already composed
let fi = "\u{FB01}".utf8Normalized(.nfkc)   // ligature "fi" → "fi" bytes
assert(fi == Array("fi".utf8))
let bad = "e\u{0301}".utf8NormalizationViolation(.nfc) // Index of the violation
assert(bad != nil)

API

EnumStringZillaNormalizationFormUnicode normalization form selector.3
StringProtocol+StringZilla.swift:32 ↗
enum StringZillaNormalizationForm

Values

nfd
nfc
nfkd
nfkc

Conforms to

Equatable, Hashable, RawRepresentable, Sendable
RawValue
StringProtocol+StringZilla.swift:38 ↗
typealias RawValue = sz_normal_form_t
rawValue
StringProtocol+StringZilla.swift:40 ↗
var rawValue: sz_normal_form_t { get }
init
StringProtocol+StringZilla.swift:49 ↗
init?(rawValue: sz_normal_form_t)
ClassUtf8UncasedNeedleUncased search pattern for UTF-8 strings, prepared once for many searches.2
StringProtocol+StringZilla.swift:672 ↗
final class Utf8UncasedNeedle

It is immutable once built, so one needle can search from many threads at once.

Conforms to

Sendable
init
StringProtocol+StringZilla.swift:676 ↗
init<S>(_ needle: S) where S : StringZillaViewable
findFirstFinds the first uncased match of the needle, whose byte length folding may change.
StringProtocol+StringZilla.swift:692 ↗
func findFirst<S>(in haystack: S) -> Range<S.Index>? where S : StringZillaViewable