Private/Get-NormalizedName.ps1
|
# Copyright (c) 2026 Jeffrey Snover. All rights reserved. # Licensed under the MIT License. See LICENSE file in the project root. # Name-normalization parity contract (t/1894#3, Main-TL condition t/1896#2 cond.1). # MUST stay byte-for-byte identical to D1's normalizeName() in lib/entities/nameResolver.ts: # B (this indexer) and D1 (query-time resolver) have to normalize the same way, or a name # B indexes won't match what D1 resolves and mentions silently fail to link. # # Exact rule, in order — apply to BOTH sides of every comparison (surface token AND alias): # 1. Unicode NFC # 2. ToLowerInvariant (locale-independent; == JS toLowerCase for Latin script) # 3. Collapse each maximal run of the PINNED whitespace set to a single U+0020: # {U+0009 TAB, U+000A LF, U+000B VT, U+000C FF, U+000D CR, U+0020 SPACE, U+00A0 NBSP}. # Pinned explicitly — do NOT use \s / \p{White_Space}; the PS and JS engines differ at # the edges. # 4. Trim. # Then compare with exact string equality. NO stemming, NO diacritic/accent folding, NO # punctuation stripping (widening breaks §5 refusal discipline). Dot-sourced — do NOT export. # The pinned whitespace code points, as an explicit numeric set. The regex character class # is built from these so source never embeds literal control chars (which corrupt silently). # Exposed module-scoped so the indexer's in-text separator pattern references the SAME set. $script:PinnedWhitespaceCodes = @(0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0xA0) $script:PinnedWhitespaceClass = '[' + ( ($script:PinnedWhitespaceCodes | ForEach-Object { '\u{0:X4}' -f $_ }) -join '' ) + ']' function Get-NormalizedName { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [AllowEmptyString()] [string]$Name ) Set-StrictMode -Version Latest $s = $Name.Normalize([System.Text.NormalizationForm]::FormC).ToLowerInvariant() $s = [regex]::Replace($s, "$script:PinnedWhitespaceClass+", ' ') return $s.Trim() } |