Skip to content
Talk to our solutions team

`strings`, text

strings is text manipulation. Twenty-nine functions, and the organising convention is worth learning once: a leading I means case-insensitive. Contains and IContains, PhraseCount and IPhraseCount, and so on.

On extracted documents, prefer the I forms unless a case difference is itself meaningful. OCR capitalisation is not evidence of anything.

strings.ToUpper(s) → string · strings.ToLower(s) → string · strings.ToTitle(s) → string

Section titled “strings.ToUpper(s) → string · strings.ToLower(s) → string · strings.ToTitle(s) → string”
ParameterTypeMeaning
sstringThe text to convert

ToTitle capitalises the first letter of each word and lowers the rest, so an all-caps name read from a form comes out as a name.

rule NormaliseBorrowerName "store the name in title case" {
when
in.Has("borrower.name")
then
out.Set("borrowerName", strings.ToTitle(in.Str("borrower.name", "")));
}
facts
{ "borrower": { "name": "JOHN SMITH" } }
out
{ "borrowerName": "John Smith" }

Removes leading and trailing whitespace.

ParameterTypeMeaning
sstringThe text to trim

The length of the text in bytes: an accented letter counts as two.

ParameterTypeMeaning
sstringThe text to measure

strings.Contains(s, substr) → bool · strings.IContains(s, substr) → bool

Section titled “strings.Contains(s, substr) → bool · strings.IContains(s, substr) → bool”

Reports whether s contains substr. IContains ignores case.

ParameterTypeMeaning
sstringThe text to search
substrstringWhat to look for
rule TrustDeedPresent "the document mentions a deed of trust" {
when
strings.IContains(bbox.Doc().AllText().Str(), "deed of trust")
then
out.Set("instrumentType", "deed_of_trust");
}

strings.HasPrefix(s, prefix) → bool · strings.HasSuffix(s, suffix) → bool

Section titled “strings.HasPrefix(s, prefix) → bool · strings.HasSuffix(s, suffix) → bool”
ParameterTypeMeaning
sstringThe text to test
prefix / suffixstringWhat to test for
rule ServiceAccount "identifiers starting with svc- are service accounts" {
when
strings.HasPrefix(in.Str("actor.id", ""), "svc-")
then
out.Set("actorKind", "service");
}
facts
{ "actor": { "id": "svc-billing" } }
out
{ "actorKind": "service" }
ParameterTypeMeaning
sstringThe text to split
sepstringThe separator
ParameterTypeMeaning
elemslist of stringThe pieces
sepstringThe separator to place between them
rule ListTags "the caller's semicolon list, comma-separated" {
when
in.Has("ticket.tags")
then
out.Set("tags", strings.Join(strings.Split(in.Str("ticket.tags", ""), ";"), ", "));
out.Set("tagCount", strings.Split(in.Str("ticket.tags", ""), ";").Len());
}
facts
{ "ticket": { "tags": "billing;urgent;vip" } }
out
{ "tags": "billing, urgent, vip", "tagCount": 3 }

Count a split with .Len(); array.Len needs the list wrapped in to.List first.

Replaces every occurrence of old with new.

ParameterTypeMeaning
sstringThe text
oldstringWhat to replace
newstringWhat to replace it with

strings.ReplaceChars(s, replacement, charset) → string

Section titled “strings.ReplaceChars(s, replacement, charset) → string”

Replaces characters rather than a substring: every character present in charset becomes replacement, and everything else is kept.

ParameterTypeMeaning
sstringThe text
replacementstringWhat each matched character becomes
charsetstringThe set of characters to replace

The cleaning function for identifiers read off a document, where the separators vary run to run.

rule NormaliseSSN "strip separators from the tax identifier" {
when
in.Has("applicant.ssn")
then
out.Set("ssn", strings.ReplaceChars(in.Str("applicant.ssn", ""), "", "- ."));
}
facts
{ "applicant": { "ssn": "123-45 6789" } }
out
{ "ssn": "123456789" }

Four forms, and choosing the right one saves a lot of post-processing. Patterns use Go’s regular expression syntax; inside a GRL string a backslash is doubled, "\\d+". An invalid pattern never raises: it matches nothing, finds "", or leaves the text unchanged.

Reports whether pattern matches anywhere in s. Anchor with ^ and $ to test the whole text.

ParameterTypeMeaning
patternstringThe regular expression
sstringThe text to test
rule LoanNumberWellFormed "loan numbers are three letters then eight digits" {
when
in.Has("loan.number") &&
!strings.MatchRegex("^[A-Z]{3}[0-9]{8}$", in.Str("loan.number", ""))
then
findings.Error("LoanNumberWellFormed", "format", "Loan number does not match the expected shape");
out.Set("loanNumberValid", false);
}
facts
{ "loan": { "number": "AB-1234" } }
out
{ "loanNumberValid": false }

Returns the first match, or the empty string when there is none.

ParameterTypeMeaning
patternstringThe regular expression
sstringThe text to search

strings.FindRegex("\\d+", "order 12345 confirmed") returns "12345".

strings.FindRegexGroups(pattern, s) → map

Section titled “strings.FindRegexGroups(pattern, s) → map”

Returns the named capture groups of the first match, as a map of text. Unnamed groups are ignored, and no match returns an empty map.

ParameterTypeMeaning
patternstringThe regular expression, with (?P<name>...) groups
sstringThe text to search

The map’s values are text, which the map functions do not read, and out.Set writes the whole map as its text form ("map[area:512 line:0199]") rather than as an object. To read one group, use ReplaceRegex with ${name}, below.

strings.ExtractRegex(pattern, s, sep) → string

Section titled “strings.ExtractRegex(pattern, s, sep) → string”

Finds all captures, named or unnamed, across all matches, and joins the non-empty ones with sep.

ParameterTypeMeaning
patternstringThe regular expression
sstringThe text to search
sepstringThe separator to join captures with

strings.ExtractRegex("(\\d+)", "order 123 item 456", " ") returns "123 456".

strings.ReplaceRegex(pattern, s, replacement) → string

Section titled “strings.ReplaceRegex(pattern, s, replacement) → string”

Replaces all matches. $1, $2 refer to positional groups and ${name} to named ones.

ParameterTypeMeaning
patternstringThe regular expression
sstringThe text
replacementstringThe replacement, which may reference groups
rule ReformatDate "turn MM/DD/YYYY into ISO" {
when
strings.MatchRegex("^[0-9]{2}/[0-9]{2}/[0-9]{4}$", in.Str("raw.date", ""))
then
out.Set("date",
strings.ReplaceRegex("^(?P<m>[0-9]{2})/(?P<d>[0-9]{2})/(?P<y>[0-9]{4})$",
in.Str("raw.date", ""), "${y}-${m}-${d}"));
}
facts
{ "raw": { "date": "03/15/2026" } }
out
{ "date": "2026-03-15" }

Matching the whole text and replacing it with one group is how a rule reads a single capture:

rule AreaCode "the area code of the contact number" {
when
strings.MatchRegex("[0-9]{3}[-. ][0-9]{3}[-. ][0-9]{4}", in.Str("contact.phone", ""))
then
out.Set("areaCode",
strings.ReplaceRegex("^.*?(?P<area>[0-9]{3})[-. ][0-9]{3}[-. ][0-9]{4}.*$",
in.Str("contact.phone", ""), "${area}"));
}
facts
{ "contact": { "phone": "Call 512-555-0199 after nine" } }
out
{ "areaCode": "512" }

A phrase here may contain several words, which is what separates these from the substring tests: they are built for looking for real language in a body of extracted text. Matching is exact text, not fuzzy.

strings.PhraseCount(text, phrase) → int64 · strings.IPhraseCount(text, phrase) → int64

Section titled “strings.PhraseCount(text, phrase) → int64 · strings.IPhraseCount(text, phrase) → int64”

How many times one phrase occurs.

ParameterTypeMeaning
textstringThe text to search
phrasestringThe phrase to count

strings.CountPhrases(text, phrases...) → int64 · strings.ICountPhrases(text, phrases...) → int64

Section titled “strings.CountPhrases(text, phrases...) → int64 · strings.ICountPhrases(text, phrases...) → int64”

The total occurrences of several phrases, a sum across all of them, not a count of how many were present.

ParameterTypeMeaning
textstringThe text to search
phrasesstring, variadicThe phrases to count
rule HeavilyRedacted "many redaction markers suggest a redacted copy" {
when
strings.ICountPhrases(bbox.Doc().AllText().Str(), "REDACTED", "[REMOVED]", "XXXXX") > 10
then
findings.Warn("HeavilyRedacted", "provenance", "Document appears heavily redacted");
}

strings.HasAllPhrases(text, phrases...) → bool · strings.IHasAllPhrases(text, phrases...) → bool

Section titled “strings.HasAllPhrases(text, phrases...) → bool · strings.IHasAllPhrases(text, phrases...) → bool”

Reports whether every phrase is present.

ParameterTypeMeaning
textstringThe text to search
phrasesstring, variadicThe phrases that must all appear
rule NoticeComplete "the notice carries all its required sections" {
when
strings.IHasAllPhrases(in.Str("notice.body", ""),
"Loan Terms", "Projected Payments", "Costs at Closing")
then
out.Set("formComplete", true);
}
facts
{ "notice": { "body": "LOAN TERMS ... projected payments ... Costs at Closing" } }
out
{ "formComplete": true }

For a presence test on a document itself, bbox.Doc().HasAll(...) is usually better. It matches fuzzily, absorbing the OCR variation these exact-match functions will not.

strings.PhraseLineNum(text, phrase) → int64 · strings.IPhraseLineNum(text, phrase) → int64

Section titled “strings.PhraseLineNum(text, phrase) → int64 · strings.IPhraseLineNum(text, phrase) → int64”

The 1-based line number where the phrase first appears, or 0 when it is not found. Lines are split on newlines.

ParameterTypeMeaning
textstringThe text to search
phrasestringThe phrase to locate

Zero means “not found”. It is not a line number, since lines start at one.

strings.PhraseLineNums(text, phrase) → list of int64 · strings.IPhraseLineNums(text, phrase) → list of int64

Section titled “strings.PhraseLineNums(text, phrase) → list of int64 · strings.IPhraseLineNums(text, phrase) → list of int64”

Every 1-based line number where the phrase appears.

ParameterTypeMeaning
textstringThe text to search
phrasestringThe phrase to locate
rule SignatureBlockPosition "the signature block should be near the end" {
when
strings.IPhraseLineNum(bbox.Doc().AllText().Str(), "IN WITNESS WHEREOF") > 0 &&
strings.IPhraseLineNum(bbox.Doc().AllText().Str(), "IN WITNESS WHEREOF") <
math.Round(to.Num(bbox.Doc().LineCount()) * 0.5, 0)
then
findings.Warn("SignatureBlockPosition", "structure",
"Signature block appears in the first half of the document");
}

strings.Sprintf(format, args...) → string

Section titled “strings.Sprintf(format, args...) → string”

Formats a string.

ParameterTypeMeaning
formatstringThe format, with %s, %d, %f and the usual verbs
argsany, variadicThe values

Mostly used to build a readable finding message.

rule AmountOverLimit "message names the actual figure" {
when
in.Double("loan.amount", 0.0) > 500000.0
then
findings.Warn("AmountOverLimit", "underwriting",
strings.Sprintf("Loan amount %s exceeds the 500,000 limit",
to.Grouped(in.Double("loan.amount", 0.0), 2)));
out.Set("limitMessage",
strings.Sprintf("Loan amount %s exceeds the 500,000 limit",
to.Grouped(in.Double("loan.amount", 0.0), 2)));
}
facts
{ "loan": { "amount": 612500.0 } }
out
{ "limitMessage": "Loan amount 612,500.00 exceeds the 500,000 limit" }

Prefer to.Grouped / to.Fixed for the numbers inside a message rather than %f, which prints float noise.