XXXX redacted
You've been given a contract from a three letter abbreviation government agency. They want a program to automatically redact sensitive information from documents to be released to the public. They want fine control over what gets redacted though.
Given a piece of free-form, possibly Unicode text, (assume text only, no markup or formatting codes) they want to be able to redact: whole words, (case sensitive or insensitive) or partial words, (case sensitive or insensitive). Further, they want the option to "overkill" redact a partial word. Overkill redact means if the word contains the redact target, even if is only part of the word, redact the entire word.
For our purposes, a "word" here, means: a character group, separated by white-space and possibly punctuation; not necessarily strictly alphabetic characters. To "redact" a word or partial word, replace each character in the redaction target with a capital letter 'X'. There should be the same number of graphemes in the final redacted word as there were in the non-redacted word.
- Task
Write a procedure to "redact" a given piece of text. Your procedure should take the text (or a link to it), the redaction target (or a link to it) and the redaction options. It need not be a single routine, as long as there is some way to programmatically select which operation will be performed. It may be invoked from a command line or as an internal routine, but it should be separately invokable, not just a hard coded block.
The given strings are enclosed in square brackets to denote them. The brackets should not be counted as part of the strings.
Using the test string: [Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom.]
Show the redacted sentence for each of the redaction target strings [Tom] & [tom] using the following options:
- Whole word
- Whole word, Case insensitive
- Partial word
- Partial word, Case insensitive
- Partial word, Overkill
- Partial word, Case insensitive, Overkill
Note that some combinations don't, or at least, shouldn't really differ from less specific combination. E.G. "Whole word, Overkill" should be theoretically be exactly the same as "Whole word".
Extra kudos for not including adjoining punctuation during "Overkill" redaction.
Extra kudos if the redaction target can contain non-letter characters.
The demo strings use the abbreviations w/p for whole/partial word, i/s for case insensitive/sensitive, n/o for normal/overkill. You are not required to use those, or any abbreviation. They are just for display, though may be useful to show what operation you are intending to perform.
Ideal expected output (adjoining punctuation untouched):
Redact 'Tom': [w|s|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [p|s|n] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [p|s|o] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX.
Redact 'tom': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [p|s|n] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX.
- Stretch
Complex Unicode: Using the test string: [๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ] and the redaction strings: [๐จ] and [๐จโ๐ฉโ๐ฆ]
Show the redacted strings when using the option "Whole word" (Case sensitivity shouldn't matter.) A single grapheme should be replaced by a single 'X'.
๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จ' [w] ๐ง X ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จโ๐ฉโ๐ฆ' [w] ๐ง ๐จ ๐ง X
AppleScript
ASObjC
This uses ASObjC to access macOS's Foundation framework's regex and text-replacement methods. The methods support ICU-compatible regular expressions.
<lang applescript>use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later use framework "Foundation" use scripting additions
on redact(theText, redactionTargets, options)
set |โ| to current application -- Set up a regex search pattern for the target or list of targets supplied. -- Since it has to be able to match grapheme characters which may be combinations of -- others in the same string, include catches for "Zero Width Joiner" characters. set targets to |โ|'s class "NSMutableArray"'s arrayWithArray:(redactionTargets as list) repeat with thisTarget in targets set thisTarget's contents to (|โ|'s class "NSRegularExpression"'s escapedPatternForString:(thisTarget)) end repeat set targetPattern to "(?<!\\u200d)(?:" & (targets's componentsJoinedByString:("|")) & ")(?!\\u200d)" -- If necessary, modify the pattern according to the requested options. Only "w", "o", "i", and "s" need attention. if (options contains "w") then -- Don't match where preceded or followed by either a hyphen or anything which isn't punctuation or white space. set targetPattern to "(?<![-[^[:punct:]\\s]])" & targetPattern & "(?![-[^[:punct:]\\s]])" else if (options contains "o") then -- Include any preceding or following run of hyphens and/or non-(punctuation or white-space). set targetPattern to "[-[^[:punct:]\\s]]*" & targetPattern & "[-[^[:punct:]\\s]]*+" end if -- Default to case-insensitivity as in vanilla AppleScript unless otherwise indicated by option or AS 'considering' attribute. if ((options contains "i") or ((options does not contain "s") and ("i" = "I"))) then ยฌ set targetPattern to "(?i)" & targetPattern -- Locate all the matches in the text. set mutableText to |โ|'s class "NSMutableString"'s stringWithString:(theText) set regexObject to |โ|'s class "NSRegularExpression"'s regularExpressionWithPattern:(targetPattern) ยฌ options:(0) |error|:(missing value) set matchObjects to regexObject's matchesInString:(mutableText) options:(0) range:({0, mutableText's |length|()}) set matchRanges to matchObjects's valueForKey:("range") -- Replace each character or grapheme in the matched ranges with "X". set regexSearch to |โ|'s NSRegularExpressionSearch repeat with i from (count matchRanges) to 1 by -1 tell mutableText to replaceOccurrencesOfString:(".(?:\\u200d.)*+") withString:("X") ยฌ options:(regexSearch) range:(item i of matchRanges) end repeat return mutableText as text
end redact
-- Test code: set theText to "Tom? Toms bottom tomato is in his stomach while playing the \"Tom-tom\" brand tom-toms. That's so tom." set output to {} repeat with redactionTarget in {"Tom", "tom"}
set end of output to "Redact " & redactionTarget & ":" repeat with options in {"[w|s|n]", "[w|i|n]", "[p|s|n]", "[p|i|n]", "[p|s|o]", "[p|i|o]"} set end of output to options & ": " & redact(theText, redactionTarget, options) end repeat set end of output to ""
end repeat set astid to AppleScript's text item delimiters set AppleScript's text item delimiters to linefeed set output to output as text set AppleScript's text item delimiters to astid return output</lang>
- Output:
<lang applescript>"Redact Tom: [w|s|n]: XXX? Toms bottom tomato is in his stomach while playing the \"Tom-tom\" brand tom-toms. That's so tom. [w|i|n]: XXX? Toms bottom tomato is in his stomach while playing the \"Tom-tom\" brand tom-toms. That's so XXX. [p|s|n]: XXX? XXXs bottom tomato is in his stomach while playing the \"XXX-tom\" brand tom-toms. That's so tom. [p|i|n]: XXX? XXXs botXXX XXXato is in his sXXXach while playing the \"XXX-XXX\" brand XXX-XXXs. That's so XXX. [p|s|o]: XXX? XXXX bottom tomato is in his stomach while playing the \"XXXXXXX\" brand tom-toms. That's so tom. [p|i|o]: XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the \"XXXXXXX\" brand XXXXXXXX. That's so XXX.
Redact tom: [w|s|n]: Tom? Toms bottom tomato is in his stomach while playing the \"Tom-tom\" brand tom-toms. That's so XXX. [w|i|n]: XXX? Toms bottom tomato is in his stomach while playing the \"Tom-tom\" brand tom-toms. That's so XXX. [p|s|n]: Tom? Toms botXXX XXXato is in his sXXXach while playing the \"Tom-XXX\" brand XXX-XXXs. That's so XXX. [p|i|n]: XXX? XXXs botXXX XXXato is in his sXXXach while playing the \"XXX-XXX\" brand XXX-XXXs. That's so XXX. [p|s|o]: Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the \"XXXXXXX\" brand XXXXXXXX. That's so XXX. [p|i|o]: XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the \"XXXXXXX\" brand XXXXXXXX. That's so XXX. "</lang> Or with the grapheme text: <lang applescript>set graphemeText to "๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ" set output to {} repeat with redactionTarget in {"๐จ", "๐จโ๐ฉโ๐ฆ"}
set end of output to "Redact " & redactionTarget & ":" set end of output to "[w]: " & redact(graphemeText, redactionTarget, "[w]") set end of output to ""
end repeat set astid to AppleScript's text item delimiters set AppleScript's text item delimiters to linefeed set output to output as text set AppleScript's text item delimiters to astid return output</lang>
- Output:
<lang applescript>"Redact ๐จ: [w]: ๐ง X ๐ง ๐จโ๐ฉโ๐ฆ
Redact ๐จโ๐ฉโ๐ฆ: [w]: ๐ง ๐จ ๐ง X "</lang>
Vanilla (core language only)
The above uses ASObjC to take advantage of the Foundation framework's regex functions. But the core AppleScript language is itself perfectly capable of performing the task, albeit with somewhat more code. A fairly recent macOS version's needed for the grapheme characters to be recognised and handled satisfactorily (macOS 10.14's fine), but with plain text, the code below works on any system since Mac OS X 10.5. On macOS 10.14, it's about twice as fast as the ASObjC.
Test code and output as above.
<lang applescript>on redact(theText, redactionTargets, options)
(* Script object containing the basic process. *) script default property textItems : missing value property outputText : theText -- Replace every instance of each of the passed redaction targets with an "X" sequence of the same length. on redact() set astid to AppleScript's text item delimiters repeat with thisTarget in (redactionTargets as list) set AppleScript's text item delimiters to thisTarget's contents set my textItems to my outputText's text items applyOption() set AppleScript's text item delimiters to getXs(count thisTarget) set my outputText to my textItems as text end repeat set astid to AppleScript's text item delimiters end redact on applyOption() end applyOption on getXs(targetLength) set Xs to "" repeat targetLength times set Xs to Xs & "X" end repeat return Xs end getXs end script (* Child script objects with their own applyOption() handlers for word-match and overkill. *) script wordMatch property parent : default property newTextItems : missing value -- Derive new text items from those just extracted with the current delimiter, losing any delimitation within words. on applyOption() set my newTextItems to {} set i to 1 repeat with j from 2 to (count my textItems) set precedingExtract to text from text item i to text item (j - 1) of my outputText -- Substring from the text. set thisTextItem to item j of my textItems -- Text item from the list. if not ยฌ ((precedingExtract ends with "-") or ยฌ (((count precedingExtract's words) > 0) and (precedingExtract ends with precedingExtract's last word)) or ยฌ (thisTextItem begins with "-") or ยฌ (((count thisTextItem's words) > 0) and (thisTextItem begins with thisTextItem's first word))) then set end of my newTextItems to precedingExtract set i to j end if end repeat set end of my newTextItems to text from text item i to text item j of my outputText set my textItems to my newTextItems end applyOption end script script overkill property parent : default -- Where the extracted text items are delimited within words, replace the word stumps' characters with "X"s. on applyOption() repeat with i from 2 to (count my textItems) set precedingTextItem to item (i - 1) of my textItems if ((count precedingTextItem's words) > 0) then set lastword to precedingTextItem's last word if ((precedingTextItem ends with lastword) or (precedingTextItem ends with (lastword & "-"))) then set editLength to (count text from last word to end of precedingTextItem) set Xs to getXs(editLength) if ((count precedingTextItem) > editLength) then ยฌ set Xs to text 1 thru -(editLength + 1) of precedingTextItem & Xs set item (i - 1) of my textItems to Xs end if else if ((precedingTextItem is "-") and (i > 2)) then -- Hyphen between two target instances. set item (i - 1) of my textItems to "X" end if set thisTextItem to item i of my textItems if ((count thisTextItem's words) > 0) then set firstWord to thisTextItem's first word if ((thisTextItem begins with firstWord) or (thisTextItem begins with ("-" & firstWord))) then set editLength to (count text 1 thru first word of thisTextItem) set Xs to getXs(editLength) if ((count thisTextItem) > editLength) then set Xs to Xs & text (editLength + 1) thru end of thisTextItem set item i of my textItems to Xs end if end if end repeat end applyOption end script (* Outer handler code. *) -- Select the script object to use as the redactor. if (options contains "w") then set redactor to wordMatch else if (options contains "o") then set redactor to overkill else set redactor to default end if -- Invoke it with the necessary text comparison attributes imposed. if ((options contains "i") or ((options does not contain "s") and ("i" = "I"))) then considering white space and punctuation but ignoring case tell redactor to redact() end considering else considering white space, punctuation and case tell redactor to redact() end considering end if return redactor's outputText
end redact</lang>
Go
Go has a problem with zero width joiner (ZWJ) emojis such as the final one in the test string which is not recognized as a single 'character' by the language as it consists of five Unicode code-points (or 'runes') instead of one. This problem is aggravated (as here) when one of the constituents of the ZWJ emoji happens to be a 'normal' emoji contained within the same test string!
Care is therefore needed to ensure that when a normal emoji is being redacted it doesn't also redact one of the constituents of a ZWJ emoji.
To get the number of 'X's right where a ZWJ emoji or other character combination is being replaced, a third party library function is used which counts the number of graphemes in a string, as required by the task. <lang go>package main
import (
"fmt" "github.com/rivo/uniseg" "log" "regexp" "strings"
)
func join(words, seps []string) string {
lw := len(words) ls := len(seps) if lw != ls+1 { log.Fatal("mismatch between number of words and separators") } var sb strings.Builder for i := 0; i < ls; i++ { sb.WriteString(words[i]) sb.WriteString(seps[i]) } sb.WriteString(words[lw-1]) return sb.String()
}
func redact(text, word, opts string) {
var partial, overkill bool exp := word if strings.IndexByte(opts, 'p') >= 0 { partial = true } if strings.IndexByte(opts, 'o') >= 0 { overkill = true } if strings.IndexByte(opts, 'i') >= 0 { exp = `(?i)` + exp } rgx := regexp.MustCompile(`[\s!-&(-,./:-@[-^{-~]+`) // all punctuation except -'_ seps := rgx.FindAllString(text, -1) words := rgx.Split(text, -1) rgx2 := regexp.MustCompile(exp) for i, w := range words { match := rgx2.FindString(w) // check there's a match and it's not part of a ZWJ emoji if match == "" || strings.Index(w, match+"\u200d") >= 0 || strings.Index(w, "\u200d"+match) >= 0 { continue } switch { case overkill: words[i] = strings.Repeat("X", uniseg.GraphemeClusterCount(w)) case !partial: if words[i] == match { words[i] = strings.Repeat("X", uniseg.GraphemeClusterCount(w)) } case partial: repl := strings.Repeat("X", uniseg.GraphemeClusterCount(word)) words[i] = rgx2.ReplaceAllLiteralString(w, repl) } } fmt.Printf("%s %s\n\n", opts, join(words, seps))
}
func printResults(text string, allOpts, allWords []string) {
fmt.Printf("Text: %s\n\n", text) for _, word := range allWords { fmt.Printf("Redact '%s':\n", word) for _, opts := range allOpts { redact(text, word, opts) } } fmt.Println()
}
func main() {
text := `Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom.
'Tis very tomish, don't you think?`
allOpts := []string{"[w|s|n]", "[w|i|n]", "[p|s|n]", "[p|i|n]", "[p|s|o]", "[p|i|o]"} allWords := []string{"Tom", "tom", "t"} printResults(text, allOpts, allWords)
text = "๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ" allOpts = []string{"[w]"} allWords = []string{"๐จ", "๐จโ๐ฉโ๐ฆ"} printResults(text, allOpts, allWords)
text = "Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ" allOpts = []string{"[p]", "[p|o]"} printResults(text, allOpts, allWords)
}</lang>
- Output:
Text: Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? Redact 'Tom': [w|s|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 'tom': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 't': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms boXXom XomaXo is in his sXomach while playing Xhe "Tom-Xom" brand Xom-Xoms. ThaX's so Xom. 'Tis very Xomish, don'X you Xhink? [p|i|n] Xom? Xoms boXXom XomaXo is in his sXomach while playing Xhe "Xom-Xom" brand Xom-Xoms. XhaX's so Xom. 'Xis very Xomish, don'X you Xhink? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. 'Tis very XXXXXX, XXXXX you XXXXX? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. XXXX very XXXXXX, XXXXX you XXXXX? Text: ๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จ': [w] ๐ง X ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จโ๐ฉโ๐ฆ': [w] ๐ง ๐จ ๐ง X Text: Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จ': [p] Argentina๐ง๐ฆ๐น FranceX๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ [p|o] Argentina๐ง๐ฆ๐น XXXXXXXX Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จโ๐ฉโ๐ฆ': [p] Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช NetherlandsX๐ณ๐ฑ [p|o] Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช XXXXXXXXXXXXX
Julia
The solution must kludge a check with the variable "multichar" to properly substitute "X" instead of "XXXX" with the last example. Otherwise Julia (v 1.4) interprets one 184-bit Unicode extended emoji character as four Unicode characters. <lang julia>function doif_equals(word, pattern, insens=false)
regex = insens ? Regex("^$pattern\$", "i") : Regex("^$pattern\$") return replace(word, regex => pattern in multichars ? "X" : "X"^length(pattern))
end doif_ci_equals(word, pattern) = doif_equals(word, pattern, true)
function doif_includes(word, pattern, insens=false)
regex = insens ? Regex(pattern, "i") : Regex(pattern) return replace(word, regex => "X"^length(pattern))
end doif_ci_includes(word, pattern) = doif_includes(word, pattern, true)
function overkill(word, pattern, insens=false)
regex = insens ? Regex(pattern, "i") : Regex(pattern) return occursin(regex, word) ? "X"^length(word) : word
end ci_overkill(word, pattern) = overkill(word, pattern, true)
const method = Dict(
"[w|s|n]" => doif_equals, "[w|i|n]" => doif_ci_equals, "[p|s|n]" => doif_includes, "[p|i|n]" => doif_ci_includes, "[p|s|o]" => overkill, "[p|i|o]" => ci_overkill
) const multichars = Set(["๐จโ๐ฉโ๐ฆ", ])
function redact(teststring, pattern)
ws = split(teststring, r"[^ \?\"\.]+") words = filter(!=(""), split(teststring, r"[\s\?\"\.]+")) fs = popfirst!(words) f = method[fs] return fs * ws[2] * mapreduce(i -> f(words[i], pattern) * ws[i + 2], *, 1:length(words))
end
const testtext = """ [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [w|i|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [p|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [p|i|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [p|s|o] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [p|i|o] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. """ const stretchtext = "[w|s|n] ๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ"
for test in [(testtext, ["Tom", "tom"]), (stretchtext, ["๐จ", "๐จโ๐ฉโ๐ฆ"])]
for pat in test[2] println("\nRedact pattern \"$pat\":") for teststring in string.(split(strip(test[1]), r"\n")) println(redact(teststring, pat)) end end println()
end
</lang>
- Output:
Redact pattern "Tom": [w|s|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [p|s|n] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [p|s|o] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. Redact pattern "tom": [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [p|s|n] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. Redact pattern "๐จ": [w|s|n] ๐ง X ๐ง ๐จโ๐ฉโ๐ฆ Redact pattern "๐จโ๐ฉโ๐ฆ": [w|s|n] ๐ง ๐จ ๐ง X
Perl
<lang perl>use strict; use warnings;
my $test = <<END; Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? END
sub redact {
my($str, $redact, %opt) = @_; my $insensitive = $opt{'i'} or 0; my $partial = $opt{'p'} or 0; my $overkill = $opt{'o'} or 0;
my $rx = $insensitive ? $partial ? $overkill ? qr/ \b{wb} ((?i)[-\w_]* [\w*']* $redact [-'\w]* \S*?) \b{wb} /x : qr/ ((?i)$redact) /x : qr/ \b{wb}(?<!-) ((?i)$redact) (?!-)\b{wb} /x : $partial ? $overkill ? qr/ \b{wb} ([-\w]* [\w*']* $redact [-'\w]* \S*?) \b{wb} /x : qr/ ($redact) /x : qr/ \b{wb}(?<!-) ($redact) (?!-)\b{wb} /x ; $str =~ s/($rx)/'X' x length $1/gre;
}
for my $redact (<Tom tom t>) {
print "\nRedact '$redact':\n"; for (['[w|s|n]', {}], ['[w|i|n]', {i=>1}], ['[p|s|n]', {p=>1}], ['[p|i|n]', {p=>1, i=>1}], ['[p|s|o]', {p=>1, o=>1}], ['[p|i|o]', {p=>1, i=>1, o=>1}] ) { my($option, $opts) = @$_; no strict 'refs'; printf "%s %s\n", $option, redact($test, $redact, %$opts) }
}</lang>
- Output:
Redact 'Tom': [w|s|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 'tom': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 't': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms boXXom XomaXo is in his sXomach while playing Xhe "Tom-Xom" brand Xom-Xoms. ThaX's so Xom. 'Tis very Xomish, don'X you Xhink? [p|i|n] Xom? Xoms boXXom XomaXo is in his sXomach while playing Xhe "Xom-Xom" brand Xom-Xoms. XhaX's so Xom. 'Xis very Xomish, don'X you Xhink? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. 'Tis very XXXXXX, XXXXX you XXXXX? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. XXXX very XXXXXX, XXXXX you XXXXX?
Phix
Written on the assumption that overkill implies partial (see talk page).
utf32_length() fashioned after Reverse_a_string#Phix with added ZWJ - I do not expect it to be entirely complete.
<lang Phix>enum WHOLE,PARTIAL,OVERKILL,INSENSITIVE
constant spunc = " \r\n\t.?\"" -- spaces and punctuation
function utf32_length(sequence utf32)
integer l = length(utf32) for i=1 to l do integer ch = utf32[i]
if (ch>=0x300 and ch<=0x36f)
or (ch>=0x1dc0 and ch<=0x1dff) or (ch>=0x20d0 and ch<=0x20ff) or (ch>=0xfe20 and ch<=0xfe2f) then l -= 1
elsif ch=0x200D then -- ZERO WIDTH JOINER l -= 2
end if end for return l
end function
function redact(string text, word, integer options)
sequence t_utf32 = utf8_to_utf32(text), l_utf32 = t_utf32, w_utf32 = utf8_to_utf32(word) string opt = "[?|s]" if options>INSENSITIVE then options -= INSENSITIVE opt[4] = 'i' l_utf32 = lower(t_utf32) w_utf32 = lower(w_utf32) end if opt[2] = "wpo"[options] integer idx = 1 while true do idx = match(w_utf32,l_utf32,idx) if idx=0 then exit end if integer edx = idx+length(w_utf32)-1 if options=WHOLE then if (idx=1 or find(l_utf32[idx-1],spunc)) and (edx=length(l_utf32) or find(l_utf32[edx+1],spunc)) then t_utf32[idx..edx] = repeat('X',utf32_length(t_utf32[idx..edx])) end if elsif options=PARTIAL or options=OVERKILL then if options=OVERKILL then while idx>1 and not find(l_utf32[idx-1],spunc) do idx -= 1 end while while edx<length(l_utf32) and not find(l_utf32[edx+1],spunc) do edx += 1 end while end if t_utf32[idx..edx] = repeat('X',utf32_length(t_utf32[idx..edx])) end if idx = edx+1 end while text = utf32_to_utf8(t_utf32) return {opt,text}
end function
constant test = ` Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom.`, tests = {"Tom","tom","t"} for t=1 to length(tests) do
printf(1,"Redact %s:\n",{tests[t]}) for o=WHOLE to OVERKILL do printf(1,"%s:%s\n",redact(test,tests[t],o)) printf(1,"%s:%s\n",redact(test,tests[t],o+INSENSITIVE)) end for
end for constant ut = "๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ", fmt = """
%s
Redact ๐จ %s %s Redact ๐จโ๐ฉโ๐ฆ %s %s """ printf(1,fmt,{ut}&redact(ut,"๐จ",WHOLE)&redact(ut,"๐จโ๐ฉโ๐ฆ",WHOLE))</lang>
- Output:
The windows console makes a complete mockery of those unicode characters, though it should look better on linux...
Redact Tom: [w|s]:XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [w|i]:XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [p|s]:XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. [p|i]:XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [o|s]:XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. [o|i]:XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. Redact tom: [w|s]:Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [w|i]:XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [p|s]:Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. [p|i]:XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [o|s]:Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. [o|i]:XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. Redact t: [w|s]:Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [w|i]:Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [p|s]:Tom? Toms boXXom XomaXo is in his sXomach while playing Xhe "Tom-Xom" brand Xom-Xoms. ThaX's so Xom. [p|i]:Xom? Xoms boXXom XomaXo is in his sXomach while playing Xhe "Xom-Xom" brand Xom-Xoms. XhaX's so Xom. [o|s]:Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. [o|i]:XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. ยญฦยบรฆ ยญฦรฆยฟ ยญฦยบรถ ยญฦรฆยฟรรรฌยญฦรฆยฎรรรฌยญฦรฆยช Redact ยญฦรฆยฟ [w|s] ยญฦยบรฆ X ยญฦยบรถ ยญฦรฆยฟรรรฌยญฦรฆยฎรรรฌยญฦรฆยช Redact ยญฦรฆยฟรรรฌยญฦรฆยฎรรรฌยญฦรฆยช [w|s] ยญฦยบรฆ ยญฦรฆยฟ ยญฦยบรถ X
Raku
<lang perl6>sub redact ( Str $str, Str $redact, :i(:$insensitive) = False, :p(:$partial) = False, :o(:$overkill) = False ) {
my $rx = $insensitive ?? $partial ?? $overkill ?? rx/:i <?after ^ | <:Po> | \s > (<[\w<:!Po>-]>*? [\w*\']? $redact [\w*\'\w+]? \S*?) <?before $ | <:Po> | \s > / #' !! rx/:i ($redact) / !! rx/:i <?after ^ | [\s<:Po>] | \s > ($redact) <?before $ | <:Po> | \s > / !! $partial ?? $overkill ?? rx/ <?after ^ | <:Po> | \s > (<[\w<:!Po>-]>*? [\w*\']? $redact [\w*\'\w+]? \S*?) <?before $ | <:Po> | \s > / #' !! rx/ ($redact) / !! rx/ <?after ^ | [\s<:Po>] | \s > ($redact) <?before $ | <:Po> | \s > / ; $str.subst( $rx, {'X' x $0.chars}, :g )
}
my %*SUB-MAIN-OPTS = :named-anywhere;
- Operate on a given file with the given parameters
multi MAIN (
Str $file, #= File name with path Str $target, #= Redact target text string :$i = False, #= Case insensitive flag :$p = False, #= Partial words flag :$o = False #= Overkill flag ) { put $file.IO.slurp.&redact( $target, :i($i), :p($p), :o($o) ) }
- Operate on the internal strings / parameters
multi MAIN () {
- TESTING
my $test = q:to/END/; Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? END #'
for 'Tom', 'tom', 't' -> $redact { say "\nRedact '$redact':"; for '[w|s|n]', $redact, {}, '[w|i|n]', $redact, {:i}, '[p|s|n]', $redact, {:p}, '[p|i|n]', $redact, {:p, :i}, '[p|s|o]', $redact, {:p, :o}, '[p|i|o]', $redact, {:p, :i, :o} -> $option, $str, %opts { printf "%s %s\n", $option, $test.&redact($str, |%opts) } }
my $emoji = '๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ'; printf "%20s %s\n", , $emoji; printf "%20s %s\n", "Redact '๐จ' [w]", $emoji.&redact('๐จ'); printf "%20s %s\n", "Redact '๐จโ๐ฉโ๐ฆ' [w]", $emoji.&redact('๐จโ๐ฉโ๐ฆ');
# Even more complicated Unicode
$emoji = 'Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ'; printf "\n%20s %s\n", , $emoji; printf "%20s %s\n", "Redact '๐จ' [p]", $emoji.&redact('๐จ', :p); printf "%20s %s\n", "Redact '๐จโ๐ฉโ๐ฆ' [p]", $emoji.&redact('๐จโ๐ฉโ๐ฆ', :p); printf "%20s %s\n", "Redact '๐จ' [p|o]", $emoji.&redact('๐จ', :p, :o); printf "%20s %s\n", "Redact '๐จโ๐ฉโ๐ฆ' [p|o]", $emoji.&redact('๐จโ๐ฉโ๐ฆ', :p, :o);
}</lang>
- Output:
Redact 'Tom': [w|s|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 'tom': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 't': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms boXXom XomaXo is in his sXomach while playing Xhe "Tom-Xom" brand Xom-Xoms. ThaX's so Xom. 'Tis very Xomish, don'X you Xhink? [p|i|n] Xom? Xoms boXXom XomaXo is in his sXomach while playing Xhe "Xom-Xom" brand Xom-Xoms. XhaX's so Xom. 'Xis very Xomish, don'X you Xhink? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. 'Tis very XXXXXX, XXXXX you XXXXX? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. XXXX very XXXXXX, XXXXX you XXXXX? ๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จ' [w] ๐ง X ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จโ๐ฉโ๐ฆ' [w] ๐ง ๐จ ๐ง X Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จ' [p] Argentina๐ง๐ฆ๐น FranceX๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จโ๐ฉโ๐ฆ' [p] Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช NetherlandsX๐ณ๐ฑ Redact '๐จ' [p|o] Argentina๐ง๐ฆ๐น XXXXXXXX Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จโ๐ฉโ๐ฆ' [p|o] Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช XXXXXXXXXXXXX
REXX
REXX doesn't have regular expressions, so I had to roll-my-own parsing. <lang REXX>/*REXX program redacts a string (using Xs) from a text, depending upon specified options*/
zDefault= 'Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom"' , "brand tom-toms. That's so tom."
parse arg x '~' z /*obtain optional arguments from the CL*/ if x== | x=="," then x= 'Tom tom t' /*Not specified? Then use the default.*/ if z= | z= "," then z= zDefault /* " " " " " " */ options= 'wโsโn wโiโn pโsโn pโiโn pโsโo pโiโo' /*most glyphs can be used instead of โ */ call build /*build some stemmed arrays for REDACT.*/
do j=1 for words(x); q= word(x, j) /*process each of the needle strings.*/ if j==1 then say 'haystack' z /*show a title if this is the 1at time.*/ say; say 'needle: ' q do k=1 for words(options); useOpt= word(options, k) say ' ['useOpt"]" redact(useOpt, q) end /*k*/ end /*j*/
exit 0 /*stick a fork in it, we're all done. */ /*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/ build: #= words(z); ?= 'โ'; !.=
do i=1 for #; n= word(z, i); n= elide(n, 'HEAD',1 ) n= elide(n, 'TAIL',,,-1) @.0.i= n; upper n; @.1.i= n end /*k*/; return
/*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/ elide: parse arg $, hot, LO, HI, inc; L= length($); inc= word(inc 1, 1)
if LO== then LO=L; if HI== then HI= L do k=LO for HI by inc; _= substr($, k, 1) if datatype(_, 'M') then leave; !.hot.i= !.hot.i || _ if inc==1 then $= substr($, 2) /*hot โก Heads Or Tails*/ else $= left($, length($) - 1) end /*k*/; return $ /*elides punctuation. */
/*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/ opt: parse arg wop; oU= option; upper oU wop; return pos(wop, oU) \== 0 /*โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ*/ redact: parse arg option,qq; nz=; lu= 0; ww= opt('w'); pp= \ww; ii= opt('i'); oo= opt('o')
qu= qq; upper qu; Lqq= length(qq); if ii then do; upper qq; lu= 1; end do r=1 for #; a= @.lu.r; na= @.0.r; La= length(a) if ww then if a==qq then na= copies(?, Lqq) if pp then do 1; _= pos(qq, a); if _==0 then leave nn= na; if ii then upper nn do La; _= pos(qq, nn); if _==0 then leave na= overlay(?, na, _, Lqq, ?); nn= na; if ii then upper nn end /*La*/ end /*1*/ if oo then if pos(?, na)\==0 then na= copies(?, length(na) ) nz= nz !.head.r || na || !.tail.r end /*r*/ return strip( translate(nz, 'X', ?) )</lang>
- output when using the default inputs:
haystack Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. needle: Tom [wโsโn] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [wโiโn] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [pโsโn] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. [pโiโn] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [pโsโo] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. [pโiโo] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. needle: tom [wโsโn] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [wโiโn] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. [pโsโn] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. [pโiโn] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. [pโsโo] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. [pโiโo] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. needle: t [wโsโn] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [wโiโn] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. [pโsโn] Tom? Toms boXXom XomaXo is in his sXomach while playing Xhe "Tom-Xom" brand Xom-Xoms. ThaX's so Xom. [pโiโn] Xom? Xoms boXXom XomaXo is in his sXomach while playing Xhe "Xom-Xom" brand Xom-Xoms. XhaX's so Xom. [pโsโo] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. [pโiโo] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX.
Wren
<lang ecmascript>import "/pattern" for Pattern import "/str" for Str import "/upc" for Graphemes
var join = Fn.new { |words, seps|
var lw = words.count var ls = seps.count if (lw != ls + 1) { Fiber.abort("Mismatch between number of words and separators.") } var sb = "" for (i in 0...ls) { sb = sb + words[i] sb = sb + seps[i] } sb = sb + words[lw-1] return sb
}
var redact = Fn.new { |text, word, opts|
var partial = opts.contains("p") var overkill = opts.contains("o") var insensitive = opts.contains("i") var i = " \t\n\r!\"#$\%&()*+,./:;<=>?@[\\]^`{|}~" // all punctuation except -'_ var p = Pattern.new("+1/i", 0, i) var matches = p.findAll(text) var seps = Pattern.matchesText(matches) var words = p.splitAll(text) var expr = insensitive ? Str.lower(word) : word var p2 = Pattern.new(expr) for (i in 0...words.count) { var w = words[i] var wl = insensitive ? Str.lower(w) : w var m = p2.find(wl) if (m && wl.indexOf(m.text + "\u200d") == -1 && wl.indexOf("\u200d" + m.text) == -1) { if (overkill) { words[i] = "X" * Graphemes.clusterCount(w) } else if (!partial) { if (wl == m.text) words[i] = "X" * Graphemes.clusterCount(w) } else if (partial) { var repl = "X" * Graphemes.clusterCount(word) words[i] = p2.replaceAll(wl, repl) } } } System.print("%(opts) %(join.call(words, seps))\n")
}
var printResults = Fn.new { |text, allOpts, allWords|
System.print("Text: %(text)\n") for (word in allWords) { System.print("Redact '%(word)':") for (opts in allOpts) redact.call(text, word, opts) } System.print()
}
var text = "Tom? Toms bottom tomato is in his stomach while playing the \"Tom-tom\" brand tom-toms. That's so tom. 'Tis very tomish, don't you think?" var allOpts = ["[w|s|n]", "[w|i|n]", "[p|s|n]", "[p|i|n]", "[p|s|o]", "[p|i|o]"] var allWords = ["Tom", "tom", "t"] printResults.call(text, allOpts, allWords)
text = "๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ" allOpts = ["[w]"] allWords = ["๐จ", "๐จโ๐ฉโ๐ฆ"] printResults.call(text, allOpts, allWords)
text = "Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ" allOpts = ["[p]", "[p|o]"] printResults.call(text, allOpts, allWords)</lang>
- Output:
Text: Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? Redact 'Tom': [w|s|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] XXX? XXXs bottom tomato is in his stomach while playing the "XXX-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] XXX? XXXX bottom tomato is in his stomach while playing the "XXXXXXX" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 'tom': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [w|i|n] XXX? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so XXX. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms botXXX XXXato is in his sXXXach while playing the "Tom-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|i|n] XXX? XXXs botXXX XXXato is in his sXXXach while playing the "XXX-XXX" brand XXX-XXXs. That's so XXX. 'Tis very XXXish, don't you think? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing the "XXXXXXX" brand XXXXXXXX. That's so XXX. 'Tis very XXXXXX, don't you think? Redact 't': [w|s|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [w|i|n] Tom? Toms bottom tomato is in his stomach while playing the "Tom-tom" brand tom-toms. That's so tom. 'Tis very tomish, don't you think? [p|s|n] Tom? Toms boXXom XomaXo is in his sXomach while playing Xhe "Tom-Xom" brand Xom-Xoms. ThaX's so Xom. 'Tis very Xomish, don'X you Xhink? [p|i|n] Xom? Xoms boXXom XomaXo is in his sXomach while playing Xhe "Xom-Xom" brand Xom-Xoms. XhaX's so Xom. 'Xis very Xomish, don'X you Xhink? [p|s|o] Tom? Toms XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. 'Tis very XXXXXX, XXXXX you XXXXX? [p|i|o] XXX? XXXX XXXXXX XXXXXX is in his XXXXXXX while playing XXX "XXXXXXX" brand XXXXXXXX. XXXXXX so XXX. XXXX very XXXXXX, XXXXX you XXXXX? Text: ๐ง ๐จ ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จ': [w] ๐ง X ๐ง ๐จโ๐ฉโ๐ฆ Redact '๐จโ๐ฉโ๐ฆ': [w] ๐ง ๐จ ๐ง X Text: Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จ': [p] Argentina๐ง๐ฆ๐น FranceX๐ซ๐ท Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ [p|o] Argentina๐ง๐ฆ๐น XXXXXXXX Germany๐ง๐ฉ๐ช Netherlands๐จโ๐ฉโ๐ฆ๐ณ๐ฑ Redact '๐จโ๐ฉโ๐ฆ': [p] Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช NetherlandsX๐ณ๐ฑ [p|o] Argentina๐ง๐ฆ๐น France๐จ๐ซ๐ท Germany๐ง๐ฉ๐ช XXXXXXXXXXXXX