Text Matching and Splitting
String Pattern
Introduction
StringPattern is a lightweight pattern matcher for decoded string characters.
It is intended for small internal checks such as protocol or prefix/suffix matching.
StringPattern::fromDivider() parses a syntax supporting literal text, ? for one decoded character, [a-z]
character sets, and one * divider.
A bracket set may start with ^ to invert its characters, such as [^a-z].
Subsequent carets are literal; [^^] excludes caret and [^] accepts every Unicode scalar value.
This changes the meaning of bracket sets that previously began with a literal caret.
The divider separates front and back matching parts; it is not a repeated wildcard.
Parsed patterns support backslash escapes for ?, *, [, ] and \.
The typed fromDivider() overload uses the erbsland::text::pattern element namespace to build immutable compiled
pattern data directly without parsing pattern syntax.
Full-string Globs
StringPattern::fromGlob() compiles a case-sensitive Erbsland Core glob.
It uses the same character-set syntax and backslash escapes as fromDivider(), including [^a-z] for negation.
Unlike a Divider pattern, it matches the complete candidate and permits any number of * wildcards.
Malformed brackets, invalid ranges, and unsupported escapes raise ParseError.
Both glob kinds operate on decoded characters across UTF-8, UTF-16, and UTF-32 strings without allocating during
matching.
Stars match zero or more characters, including directory separators, leading dots, and newlines.
Adjacent stars have the same meaning as one star, and ? matches exactly one decoded character.
An empty glob matches only empty text; a standalone star matches any text.
The typed fromGlob() factory accepts the existing literal, character, range, and set elements plus Star.
Star may appear multiple times.
Divider is rejected at compile time by fromGlob(), and Star is rejected by fromDivider().
No arguments creates an empty Glob.
Typed patterns retain the existing fixed-capacity limits: 16 compiled elements, 16 total ranges, and eight ranges per
set.
Each star occupies one element after adjacent stars are coalesced.
Parsed patterns use dynamic storage, limited to 65535 pooled ranges.
const auto sources = el::StringPattern::fromGlob("src/*/file?.[ch]pp"_el);
const auto matches = sources.matches("src/text/impl/file1.hpp"_el);
using namespace el::text::pattern;
static const auto logName = el::StringPattern::fromGlob(
Text{U"log-"}, Star{}, Text{U"-"}, Range{U'0', U'9'}, Star{});
Python-style Globs
StringPattern::fromPosixGlob() preserves the original Python-style glob dialect.
The name identifies this dialect; it does not promise complete POSIX conformance or filesystem traversal.
Its full-string matching and extraction behavior is identical to Glob, while parsing differs:
[abc]and[a-z]match a character or ascending range;[!a-z]negates the set. A caret is literal.A closing bracket immediately after
[or[!is a set member.[[]matches an opening bracket. Unclosed brackets are literal. A leading or trailing hyphen is literal; descending ranges contribute no matches.Backslashes are literal, not escapes.
For example, fromGlob("[^a-c]*"_el) and fromPosixGlob("[!a-c]*"_el) both reject a candidate beginning with
b.
Use fromPosixGlob() when retaining the original parser’s behavior.
kind() reports Divider, Glob, or PosixGlob according to the factory used.
Matching and Extraction
All three kinds support every matching and extraction method.
Divider patterns retain prefix/suffix boundaries; both glob kinds match and consume the complete candidate.
All comparisons are case-sensitive.
Method |
|
|
|---|---|---|
|
Accepts a matching prefix without |
Requires the complete text to match all wildcard elements. |
|
Removes the matched prefix and/or suffix, retaining the middle text. |
Removes the complete text. |
|
Returns |
Returns |
|
Returns the prefix length, or the suffix length for a suffix-only pattern. |
Returns the complete text length. |
|
Returns the index after the prefix, or the start of the suffix for a suffix-only pattern. |
Returns the end index. |
On a mismatch, matches() and trim() return false, trimmed() returns the original text, split() returns
{empty, text}, length() returns zero, and index() returns no-index.
trim() reports whether text was removed, so a successful empty glob match returns false.
That empty match has length zero and end index zero, distinguishing it from a mismatch through index() or
matches().
trim() accepts only string editors; use trimmed() to obtain a narrowed read-only string.
Lengths and indexes use bytes for UTF-8, code units for UTF-16, and code points for UTF-32.
Pattern creation uses named factories, including fromDivider() with no arguments for an empty Divider pattern.
Copying and moving retain the compiled kind in the destination; a moved-from value behaves as an empty Divider.
Example
using namespace el::text::literals;
const auto parsed = el::StringPattern::fromDivider("http?://*"_el);
const auto ok = parsed.matches("https://example.test"_el);
using namespace el::text::pattern;
static const auto staticPattern = el::StringPattern::fromDivider(
Text{U"http"},
OneChar{},
Text{U"://"},
Divider{});
Fuzzy Matching
Introduction
The text::fuzzy namespace ranks candidate strings by Damerau-Levenshtein edit distance.
Matching operates on decoded Unicode code points, and an adjacent transposition counts as one edit.
It is suitable for short suggestion lists, command names, option names, and other user-entered identifiers.
Matcher
Matcher stores the reference pattern, an optional maximum distance, an optional result limit, and a
CharCompareFn.
Distance and result limits are unbounded by default, and matching is case-sensitive unless a comparison callback is
configured.
findMatches() accepts a StringList and returns a MatchList ordered by
distance and then original candidate order.
Equivalent candidates are deduplicated using the configured comparison callback.
Match Results
Match exposes the original candidate and its
CpCount edit distance.
MatchList is an util::List of matches.
Example
auto matcher = el::text::fuzzy::Matcher{"verbsoe"_el};
matcher.setMaximumDistance(el::CpCount{2U}).setMaximumResults(el::ItemCount{3U});
const auto matches = matcher.findMatches(el::StringList{"verbose"_el, "version"_el, "quiet"_el});
String Splitters
For a single first or last separator, use the string methods described in Splitting at a Separator. They return both halves and distinguish an absent separator from empty fields.
String splitters read an owning Core string sequentially and return copy-on-write slices without copying their text.
StringSplitter is the common UTF-8 alias.
The U8StringSplitter, U16StringSplitter and U32StringSplitter variants provide the same interface for each
supported width.
Separators
A splitter accepts either one Char or a
CharSet. Each call to next() reads through the next matching separator.
Malformed encoded data is handled tolerantly like the underlying string type.
StringSplitMode::DiscardSeparator returns only
the text between separators.
Consecutive separators therefore return empty parts, and a trailing separator produces a final empty part.
StringSplitMode::KeepSeparator includes the
separator at the end of each part.
In this mode a trailing separator completes the preceding part without producing another empty part, which is useful for
line-oriented processing.
Sequential State
isAtEnd() distinguishes an empty part from the end of the sequence.
Calling next() after the end safely returns an empty string.
Calling skip() consumes the same next part as next() without constructing its shared slice; calling it after the
end has no effect.
splitCount() counts consumed parts, including empty parts and parts discarded by skip().
splitIndex() is the zero-based index of the next part and equals that count.
Calls at the end leave both values unchanged; reset() returns both to zero.
remaining() returns the unread suffix as another shared slice, and reset() restarts the splitter at the
beginning.
An empty source has one empty part.
This preserves ordinary split semantics and lets a caller observe the source once before isAtEnd() becomes true.
The UTF-8 implementation has a direct byte-search path for a single ASCII separator. ASCII bytes cannot occur inside a UTF-8 multibyte sequence, so this optimization preserves tolerant decoding behavior. Other separator sets use decoded character matching. The width-specific backends can use independent search optimizations while keeping the same public contract.
Callback Processing
forEach(function) processes all remaining parts with the current separator mode.
The callback accepts either const String &part or const String &part, ItemIndex index, using the splitter’s
string width.
The index is the zero-based source part index and continues from splitIndex() after earlier reads or skips.
Each part is consumed before its callback runs.
Return LoopStatus::Continue to process the next part, LoopStatus::Stop to return LoopResult::Stopped, or
LoopStatus::Error to return LoopResult::Error.
Reaching the end returns LoopResult::Success.
After a stop, error, or callback exception, processing can resume with the next unread part.
Callback exceptions propagate to the caller.
A null callback returns success without consuming any parts.
Calling forEach() at the end returns success without invoking the callback.
Interface
-
class Match
One fuzzy text match and its edit distance.
-
class Matcher
Find text candidates using bounded Damerau-Levenshtein distance.
Matching operates on decoded Unicode code points. Adjacent transpositions count as one edit. Results are ordered by distance and retain candidate order for ties.
Public Functions
-
Matcher() = default
Create a matcher for an empty pattern.
-
inline explicit Matcher(text::String pattern) noexcept
Create a matcher for a pattern.
- Parameters:
pattern – The text to compare with candidates.
-
Matcher &setMaximumDistance(unit::CpCount maximumDistance) noexcept
Set the maximum accepted edit distance.
-
Matcher &setMaximumResults(unit::ItemCount maximumResults) noexcept
Set the maximum number of returned matches.
-
inline CharCompareFn comparisonFn() const noexcept
Get the optional decoded-character comparison function.
-
Matcher &setComparisonFn(CharCompareFn comparisonFn) noexcept
Set the decoded-character comparison function. Empty selects exact comparison.
-
MatchList findMatches(const text::StringList &candidates) const
Find matching candidates.
- Parameters:
candidates – Candidate text in preferred tie order.
- Returns:
Accepted, deduplicated matches ordered by distance and candidate order.
-
Matcher() = default
-
struct Divider
Split the pattern into front and back matching parts.
-
struct OneChar
Match one decoded character.
-
class Set
A set element made of one or more ranges.
-
class Range
A character range element.
Public Functions
-
constexpr Range() noexcept = default
Create an empty range.
-
inline constexpr Range(const Char first, const Char last) noexcept
Create a range from two characters.
-
inline constexpr Range(const char32_t first, const char32_t last) noexcept
Create a range from two code points.
-
constexpr Range() noexcept = default
-
struct Star
Match zero or more decoded characters in a glob pattern.
-
class Text
Non-owning UTF-32 text element for static pattern construction.
Public Functions
-
constexpr Text() noexcept = default
Create an empty text element.
-
template<std::size_t N>
inline explicit constexpr Text(const char32_t (&data)[N]) noexcept Create a text element from a UTF-32 literal.
-
inline explicit constexpr Text(const std::u32string_view text) noexcept
Create a text element from a UTF-32 read-only string.
-
inline constexpr std::u32string_view view() const noexcept
Access the literal text.
-
constexpr Text() noexcept = default
-
class StringPattern
A lightweight decoded-character string pattern.
See: Text Matching and Splitting
Public Functions
-
StringPatternKind kind() const noexcept
Get the matching and extraction semantics selected by the factory.
- Returns:
The factory-selected Divider, Glob, or PosixGlob kind; moved-from patterns report Divider.
-
bool matches(const U8String &text) const noexcept
Test whether text matches this pattern.
Divider matches a prefix, or non-overlapping anchored prefix/suffix parts around its divider. Both glob kinds require complete text to match; empty globs match empty text. An empty Divider never matches.
- Parameters:
text – Decoded candidate text; matching is case-sensitive.
- Returns:
Whether the configured pattern matches.
-
bool matches(const U16String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
bool matches(const U32String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
bool trim(U8StringEditor &text) const
Remove matched text from an editable string.
Divider removes the matching prefix and/or suffix; both glob kinds clear the complete text on a match. A mismatch leaves text unchanged, and a match against empty text removes nothing.
- Parameters:
text – Text to modify.
- Returns:
Whether any text was removed.
-
bool trim(U16StringEditor &text) const
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
bool trim(U32StringEditor &text) const
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
U8String trimmed(const U8String &text) const noexcept
Return text after removing its matching parts.
Divider removes its prefix and/or suffix, retaining the middle; both glob kinds return empty text on a match.
- Parameters:
text – Text to match without modifying it.
- Returns:
The remaining text on a match, or the original text on a mismatch.
-
U16String trimmed(const U16String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
U32String trimmed(const U32String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
std::pair<U8String, U8String> split(const U8String &text) const noexcept
Split text according to this pattern’s matching boundary.
Divider returns {prefix, remainder} without a suffix, {prefix, middle} with both parts, or {leading text, suffix} with only a suffix. Both glob kinds return {text, empty} on a match.
- Parameters:
text – Text to match without modifying it.
- Returns:
The described pair on a match; {empty, text} on a mismatch.
-
std::pair<U16String, U16String> split(const U16String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
std::pair<U32String, U32String> split(const U32String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
unit::ByteCount length(const U8String &text) const noexcept
Get the native length selected by a match.
Divider returns the prefix length, or the suffix length for a suffix-only pattern. Both glob kinds return the complete text length. Units are bytes for UTF-8, code units for UTF-16, and code points for UTF-32.
- Parameters:
text – Text to match.
- Returns:
The selected length, or zero on a mismatch or an empty glob match.
-
unit::U16Count length(const U16String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
unit::CpCount length(const U32String &text) const noexcept
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.
-
unit::ByteIndex index(const U8String &text) const noexcept
Get the native boundary selected by a match.
Divider returns the index after its prefix, or the start of its suffix for a suffix-only pattern. Both glob kinds return the end index, including zero for an empty match; units follow the text’s native encoding.
- Parameters:
text – Text to match.
- Returns:
The selected index, or no-index on a mismatch.
Public Static Functions
-
static StringPattern fromDivider(const AnyString &pattern)
Parse a case-sensitive prefix/divider pattern from text of any supported width.
Without a divider, matches() accepts a matching prefix; one * divides anchored prefix and suffix parts. An empty pattern never matches. Use fromGlob() for full-string matching with multiple stars.
- Parameters:
pattern – Literals, ?, [a-z], [^a-z], and at most one *; backslash escapes ?, *, [, ], and backslash.
- Throws:
ParseError – If syntax is invalid, a star stands alone, or compiled range limits are exceeded.
- Returns:
A compiled Divider pattern.
-
template<pattern::AnyElement... Args>
static StringPattern fromDivider(const Args&... elements) Create a case-sensitive prefix/divider pattern directly from typed elements.
No elements creates a Divider pattern that never matches. Star elements are rejected at compile time.
- Template Parameters:
Args – Supported typed pattern element types.
- Parameters:
elements – The literal, character, set and optional divider elements.
- Throws:
ParseError – If the divider stands alone, repeats, or typed storage limits are exceeded.
- Returns:
A compiled Divider pattern.
-
static StringPattern fromGlob(const AnyString &pattern)
Compile an Erbsland Core full-string glob using Divider’s escaping and character-set syntax.
Stars match zero or more characters, including separators and newlines; empty globs match only empty text.
- Parameters:
pattern – Text of any supported width: literals, *, ?, [a-z], and [^a-z], with Divider’s escapes.
- Throws:
ParseError – If syntax is invalid or compiled range limits are exceeded.
- Returns:
A compiled Glob pattern.
-
template<pattern::AnyElement... Args>
static StringPattern fromGlob(const Args&... elements) Create a full-string glob directly from typed elements.
No elements matches only empty text. Divider elements are rejected at compile time.
- Template Parameters:
Args – Supported typed pattern element types.
- Parameters:
elements – The literal, character, set and any number of Star elements.
- Throws:
ParseError – If typed storage limits are exceeded or an element is invalid.
- Returns:
A compiled Glob pattern.
-
static StringPattern fromPosixGlob(const AnyString &pattern)
Compile a full-string glob using the existing Python-style dialect.
- Parameters:
pattern – Text of any supported width: *, ?, [abc], [a-z], [!a-z]; backslashes and unmatched brackets are literal.
- Throws:
ParseError – If characters are invalid or the compiled representation exceeds 65535 ranges.
- Returns:
A compiled PosixGlob; stars include separators/newlines, and an empty glob matches only empty text.
Public Static Attributes
-
static constexpr auto cMaximumElements = pattern::cMaximumElements
Maximum number of elements in a typed pattern.
-
static constexpr auto cMaximumRanges = pattern::cMaximumStaticRanges
Maximum number of total ranges in a typed pattern.
-
static constexpr auto cMaximumSetRanges = pattern::cMaximumSetRanges
Maximum number of ranges in one set element.
-
StringPatternKind kind() const noexcept
-
enum class erbsland::text::StringPatternKind : std::uint8_t
Select the matching and extraction semantics of a compiled string pattern.
See: Text Matching and Splitting
Values:
-
enumerator Divider
Match a prefix, or anchored prefix/suffix parts separated by one optional divider.
Trimming removes these parts and preserves the text between them.
-
enumerator Glob
Match the complete text with Core character-set syntax, escapes, and any number of glob wildcards.
A successful match consumes the complete text for trimming and splitting.
-
enumerator PosixGlob
Match the complete text using the Python-style glob dialect with exclamation-mark set negation.
A successful match consumes the complete text for trimming and splitting.
-
enumerator Divider
-
enum class erbsland::text::StringSplitMode : uint8_t
Select how a string splitter handles separator characters.
Values:
-
enumerator DiscardSeparator
Exclude the separator from each returned part.
-
enumerator KeepSeparator
Include the separator at the end of each returned part.
-
enumerator DiscardSeparator
-
typedef U8StringSplitter erbsland::text::StringSplitter
The common sequential UTF-8 string splitter.
-
typedef impl::StringSplitter<U16String> erbsland::text::U16StringSplitter
A sequential UTF-16 string splitter.