Documentation
¶
Overview ¶
Package normtext provides deterministic, composable text normalization: the mechanical "clean the text before you sketch it" layer for similarity and search tooling (built as the preprocessing companion to github.com/ophymx/semblance).
Every transform is a pure function from string to string. Transforms compose with Chain or, when a machine-readable identity is needed for persistence, with NewPipeline. Frozen presets (ForShingling, Aggressive) take raw text to sketch-ready text in one call.
Determinism ¶
Same input + same pipeline → same output, on every platform and in every process. Unicode-dependent transforms consult either tables generated into this repository (category snapshots, the confusables table), which shift only on a deliberate regeneration commit, or golang.org/x/text. x/text ships tables for more than one Unicode generation and selects one at build time via go1.N build constraints, so its active generation is chosen jointly by the go.mod x/text version and the building Go toolchain. Unicode's stability policies make a generation change strictly additive: previously unassigned code points gain behavior, while the normalization and case behavior of assigned code points never changes. Text containing only code points assigned in the active generation is therefore stable forever, and pure-ASCII input is unconditionally stable everywhere. A test probes generation-distinguishing code points so a toolchain upgrade that would activate a newer generation fails CI — a deliberate, reviewed change instead of a silent one. Each transform's documentation states its Unicode-version dependence.
Invalid UTF-8 is never repaired but always handled deterministically: input a transform would not change passes through byte-for-byte, and when a transform rewrites, x/text-backed and rune-deleting transforms replace invalid bytes with U+FFFD (pass-through there would break idempotence); each transform's documentation says which.
Frozen pipelines ¶
normtext output feeds stored similarity signatures, so a pipeline's definition is as load-bearing as a MinHash seed. Named presets are frozen forever under their name; improvements ship as new names. Callers persisting signatures should store the Pipeline.ID alongside them.
Index ¶
- func Apply(s string, ts ...Transform) string
- func Bytes(t Transform, b []byte) []byte
- type Pipeline
- type Step
- func CollapseSpaceStep() Step
- func FoldConfusablesStep() Step
- func FoldStep() Step
- func LowerStep() Step
- func MaskPatternStep(re, repl string) Step
- func NFCStep() Step
- func NFKCStep() Step
- func Named(name string, t Transform) Step
- func NewlinesStep() Step
- func StraightenPunctStep() Step
- func StripDiacriticsStep() Step
- func StripInvisibleStep() Step
- type Transform
- func Aggressive() Transform
- func Chain(ts ...Transform) Transform
- func CollapseSpace() Transform
- func Fold() Transform
- func FoldConfusables() Transform
- func ForShingling() Transform
- func Lower() Transform
- func MaskPattern(re, repl string) Transform
- func NFC() Transform
- func NFKC() Transform
- func Newlines() Transform
- func StraightenPunct() Transform
- func StripDiacritics() Transform
- func StripInvisible() Transform
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Bytes ¶
Bytes adapts a Transform to []byte without copying the input: b is viewed as a string zero-copy, and fresh bytes are allocated only if t changes it; otherwise b itself is returned.
The string passed to t aliases b: t must not retain it past the call, and b must not be mutated while Bytes runs. All transforms in this package satisfy the former.
Types ¶
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline is a Transform with a machine-readable identity, for callers who persist signatures. Two pipelines with equal IDs produce equal output for every input, forever — built-in step names are part of the frozen surface, and callers own that guarantee for Named steps.
Pipeline values are immutable and safe for concurrent use.
func AggressivePipeline ¶
func AggressivePipeline() Pipeline
AggressivePipeline is Aggressive with its Pipeline identity.
func ForShinglingPipeline ¶
func ForShinglingPipeline() Pipeline
ForShinglingPipeline is ForShingling with its Pipeline identity.
func NewPipeline ¶
NewPipeline builds a Pipeline that applies steps left to right. It panics on a zero-value Step.
func (Pipeline) ID ¶
ID returns the pipeline's identity string, e.g.
normtext/1:stripinvisible|nfc|lower|straightenpunct|collapsespace
Callers persisting similarity signatures should store this alongside them (next to the MinHash seed/k): same ID ⇒ same normalization behavior.
type Step ¶
type Step struct {
// contains filtered or unexported fields
}
Step is a named Transform: the unit from which a Pipeline is built. Built-in transforms have Step constructors with stable, frozen names (NFCStep, LowerStep, ...); custom transforms enter a Pipeline via Named. The zero Step is invalid.
func CollapseSpaceStep ¶
func CollapseSpaceStep() Step
CollapseSpaceStep is CollapseSpace as a Pipeline step, named "collapsespace".
func FoldConfusablesStep ¶
func FoldConfusablesStep() Step
FoldConfusablesStep is FoldConfusables as a Pipeline step, named "foldconfusables".
func MaskPatternStep ¶
MaskPatternStep is MaskPattern as a Pipeline step. Its name embeds the regex source and replacement — "mask(<re>=><repl>)" — because a caller-supplied pattern cannot be frozen under a fixed name. The name is informative, not a parseable grammar (re may contain any punctuation except '|', which Named forbids; use Named with your own name for patterns containing '|').
func Named ¶
Named creates a Step from a caller-chosen name and transform. The caller owns keeping the (name → behavior) mapping frozen: a stored Pipeline ID containing this name is only meaningful while the name denotes the same behavior.
Named panics if name is empty or contains '|' (the ID separator), or if t is nil.
func NewlinesStep ¶
func NewlinesStep() Step
NewlinesStep is Newlines as a Pipeline step, named "newlines".
func StraightenPunctStep ¶
func StraightenPunctStep() Step
StraightenPunctStep is StraightenPunct as a Pipeline step, named "straightenpunct".
func StripDiacriticsStep ¶
func StripDiacriticsStep() Step
StripDiacriticsStep is StripDiacritics as a Pipeline step, named "stripdiacritics".
func StripInvisibleStep ¶
func StripInvisibleStep() Step
StripInvisibleStep is StripInvisible as a Pipeline step, named "stripinvisible".
type Transform ¶
Transform maps text to normalized text.
Transforms are pure and safe for concurrent use. A transform that would make no change returns its input unchanged — the same string, same backing array. For already-clean ASCII input every transform in this package does so without allocating (asserted in tests); non-ASCII no-op input may cost transient scratch allocations on some transforms but still returns the original string.
func Aggressive ¶
func Aggressive() Transform
Aggressive is the recall-maximizing preset for fuzzy / mutation-robust matching: compatibility folding, full case folding, diacritic stripping, confusable (homoglyph) folding. Exact sequence (frozen):
stripinvisible | nfkc | fold | stripdiacritics | straightenpunct | foldconfusables | fold | nfc | collapsespace
ID: normtext/1:stripinvisible|nfkc|fold|stripdiacritics|straightenpunct|foldconfusables|fold|nfc|collapsespace
Because UTS #39 skeletons map some ASCII ("m" → "rn", "1" → "l"), Aggressive rewrites even clean English text: its output is for matching only, never display, and the clean-ASCII free-pass-through property does not apply (output is a fixed point instead — applying Aggressive to its own output is a zero-alloc no-op).
Unicode data: x/text (see the package Determinism notes) for NFKC/NFC, folding, and diacritics; the repo-generated confusables table; the other steps are unconditionally stable.
func Chain ¶
Chain composes transforms left to right into a single Transform. Chain() with no arguments returns the identity transform.
func CollapseSpace ¶
func CollapseSpace() Transform
CollapseSpace returns a Transform that replaces every run of Unicode whitespace (the White_Space set: spaces, tabs, newlines, NBSP, ideographic space, ...) with a single U+0020 and trims leading/trailing whitespace.
Idempotent. Unconditionally stable: the whitespace set is frozen in this package, no external Unicode table. Already-collapsed input passes through allocation-free. Invalid UTF-8 passes through unchanged.
func Fold ¶
func Fold() Transform
Fold returns a Transform applying full Unicode case folding (x/text/cases), the caseless-matching normalization: ß → ss, Σ/σ/ς → σ. More aggressive than Lower; output is for matching, not display.
Idempotent. Unicode data: x/text (see the package Determinism notes). ASCII input is unconditionally stable (folding is lowercasing there) and passes through allocation-free when already lowercase. Invalid UTF-8 in the non-ASCII path may be replaced with U+FFFD (deterministically).
func FoldConfusables ¶
func FoldConfusables() Transform
FoldConfusables returns a Transform that maps text to its UTS #39 skeleton: NFD, replace each code point with its confusable prototype, NFD again. Visually confusable strings — homoglyph spoofs like Cyrillic "раураl" for "paypal" — fold to identical skeletons.
The skeleton maps some ASCII too ("0" → "O", "1"/"I"/"|" → "l", "m" → "rn"), so this transform rewrites even plain English text. Output is for matching only, never display. For case-insensitive matching, case-fold BEFORE folding confusables and again after (prototypes include uppercase); the Aggressive preset does both. Output is NFD-decomposed (per UTS #39); follow with NFC if composed output matters.
Idempotent: the generated table is transitively closed over NFD + mapping at generation time, because the raw Unicode data is not (a handful of skeletons therefore differ from a literal UTS #39 implementation; see confusables_gen.go). Unicode data: the repository's generated confusables table (currently Unicode 15.0.0) — x/text ships no confusables data, so this repo is the sole pin — plus x/text for NFD (see the package Determinism notes). ASCII input containing no mapped characters passes through allocation-free. Invalid UTF-8 is handled deterministically (bytes may pass through or be replaced via the x/text normalization path).
func ForShingling ¶
func ForShingling() Transform
ForShingling is the recommended default ahead of semblance shingling: conservative and meaning-preserving. Exact sequence (frozen):
stripinvisible | nfc | lower | straightenpunct | collapsespace
ID: normtext/1:stripinvisible|nfc|lower|straightenpunct|collapsespace
Unicode data: x/text (see the package Determinism notes) for NFC and lowercasing; the other steps are unconditionally stable. ASCII input already in this form passes through allocation-free.
func Lower ¶
func Lower() Transform
Lower returns a Transform applying Unicode lowercasing (x/text/cases, language-neutral "und" rules — no Turkish/Azeri tailoring, per the mechanical-not-linguistic principle).
Idempotent. Unicode data: x/text (see the package Determinism notes). ASCII input is unconditionally stable, lowercased via a fast path, and passes through allocation-free when already lowercase. Invalid UTF-8 in the non-ASCII path may be replaced with U+FFFD (deterministically).
Note for semblance users: its word shingler already lowercases tokens (stdlib unicode.ToLower), so Lower is redundant-but-harmless ahead of word shingles; it matters ahead of char shingles and winnowing.
func MaskPattern ¶
MaskPattern returns a Transform that replaces every match of the regular expression re with repl (using regexp.Regexp.ReplaceAllString semantics, so $1-style expansions apply). Typical use: URLs, email addresses, or numbers → a fixed placeholder, so volatile substrings don't dominate similarity.
MaskPattern is the escape hatch among normtext transforms, with two caveats the frozen presets avoid (no preset includes it):
- It is not necessarily idempotent — repl may itself match re.
- It cannot be frozen by name: its Pipeline identity (MaskPatternStep) embeds the regex source and replacement, and the caller owns that (pattern → behavior) contract.
Deterministic (Go regexp is), stable across Unicode versions for ASCII patterns. Input with no match passes through unchanged; the match check may allocate transiently. Panics if re does not compile, like regexp.MustCompile.
func NFC ¶
func NFC() Transform
NFC returns a Transform applying Unicode canonical composition (NFC).
Idempotent. Unicode data: x/text (see the package Determinism notes). ASCII input is unconditionally stable and passes through allocation-free. Invalid UTF-8 passes through when no normalization is needed; when rewriting, invalid bytes become U+FFFD.
func NFKC ¶
func NFKC() Transform
NFKC returns a Transform applying Unicode compatibility composition (NFKC): ligatures, fullwidth forms, circled digits, etc. fold to their compatibility equivalents.
Idempotent. Unicode data: x/text (see the package Determinism notes). ASCII input is unconditionally stable and passes through allocation-free. Invalid UTF-8 passes through when no normalization is needed; when rewriting, invalid bytes become U+FFFD.
func Newlines ¶
func Newlines() Transform
Newlines returns a Transform that normalizes line breaks to \n: CRLF and lone CR become \n, as do NEL (U+0085), LINE SEPARATOR (U+2028), and PARAGRAPH SEPARATOR (U+2029). \n itself is untouched.
Idempotent. Unconditionally stable: fixed code-point set, no external Unicode table. Input with only \n line breaks passes through allocation-free. Invalid UTF-8 passes through unchanged.
func StraightenPunct ¶
func StraightenPunct() Transform
StraightenPunct returns a Transform that replaces typographic punctuation with ASCII equivalents:
- single quotes ‘ ’ ‚ ‛ ‹ › → '
- double quotes “ ” „ ‟ « » → "
- hyphens/dashes ‐ ‑ ‒ – — ― and minus − → -
- ellipsis … → ...
The mapping is exactly the set above — frozen in this source file, no external Unicode table, so unconditionally stable. (NFKC does not fold these: smart quotes are not compatibility equivalents of ASCII ones.)
Idempotent (output is ASCII, never remapped). ASCII input passes through allocation-free. Invalid UTF-8 passes through unchanged.
func StripDiacritics ¶
func StripDiacritics() Transform
StripDiacritics returns a Transform that removes combining marks: NFD decomposition, drop nonspacing marks (Mn), NFC recomposition. café → cafe, façade → facade. Precomposed characters without a canonical decomposition (ß, ø, đ) are unaffected.
Idempotent. Unicode data: x/text for NFD/NFC (see the package Determinism notes) and the repository's generated Mn table snapshot (see tables_gen.go). ASCII input is unconditionally stable and passes through allocation-free. Invalid UTF-8 may be replaced with U+FFFD (deterministically).
func StripInvisible ¶
func StripInvisible() Transform
StripInvisible returns a Transform that removes characters with no visual width or meaning for text comparison: Unicode format characters (Cf — zero-width space/joiner/non-joiner, bidi controls, soft hyphen, word joiner, BOM/ZWNBSP, ...) and C0/C1 control characters.
It never removes whitespace: \t, \n, \v, \f, \r, and NEL (U+0085) are preserved even though they are control characters — they belong to Newlines/CollapseSpace. (No Cf character is whitespace.)
Idempotent. Unicode data: the repository's generated Cf table snapshot (see tables_gen.go) — pinned by this repo, not by go.mod or the toolchain. ASCII input without controls is unconditionally stable and passes through allocation-free. Invalid UTF-8: input with nothing to remove passes through unchanged; otherwise invalid bytes are replaced with U+FFFD (removal could splice invalid bytes into new code points, so passthrough would break idempotence).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package code segments source-code identifiers into words, so lexical similarity survives naming-convention changes: getUserName, GetUserName, and get_user_name all normalize to "get user name".
|
Package code segments source-code identifiers into words, so lexical similarity survives naming-convention changes: getUserName, GetUserName, and get_user_name all normalize to "get user name". |
|
internal
|
|
|
genconfusables
command
Command genconfusables generates the UTS #39 confusables table (confusables_gen.go) from Unicode's confusables.txt.
|
Command genconfusables generates the UTS #39 confusables table (confusables_gen.go) from Unicode's confusables.txt. |
|
gentables
command
Command gentables snapshots the Unicode category tables normtext depends on (Mn, Cf) from the generating toolchain's stdlib into tables_gen.go, so the library's behavior is pinned by this repository rather than by whichever toolchain builds it.
|
Command gentables snapshots the Unicode category tables normtext depends on (Mn, Cf) from the generating toolchain's stdlib into tables_gen.go, so the library's behavior is pinned by this repository rather than by whichever toolchain builds it. |
|
Package markup strips HTML/XML markup, leaving normalized plain text.
|
Package markup strips HTML/XML markup, leaving normalized plain text. |
|
Package message provides structural stripping for email and NNTP (Usenet) text: quoted reply lines, signatures, and header blocks, so downstream shingles reflect a message's new text rather than its parent's.
|
Package message provides structural stripping for email and NNTP (Usenet) text: quoted reply lines, signatures, and header blocks, so downstream shingles reflect a message's new text rather than its parent's. |