Misc util function tweaks

I was going to make wordWrap use Span, but decided against it
This commit is contained in:
Daniel J. Summers 2019-03-07 20:31:58 -06:00
parent 16fd7bfde4
commit 7e36fff0f1
2 changed files with 125 additions and 66 deletions

View File

@ -64,8 +64,20 @@ let htmlToPlainTextTests =
] ]
[<Tests>] [<Tests>]
let replaceFirstTests = let sndAsStringTests =
testList "replaceFirst" [ testList "sndAsString" [
test "converts the second item to a string" {
Expect.equal (sndAsString ("a", 5)) "5" "The second part of the tuple should have been converted to a string"
}
]
module StringTests =
open PrayerTracker.Utils.String
[<Tests>]
let replaceFirstTests =
testList "String.replaceFirst" [
test "replaces the first occurrence when it is found at the beginning of the string" { test "replaces the first occurrence when it is found at the beginning of the string" {
let testString = "unit unit unit" let testString = "unit unit unit"
Expect.equal (replaceFirst "unit" "test" testString) "test unit unit" Expect.equal (replaceFirst "unit" "test" testString) "test unit unit"
@ -83,11 +95,19 @@ let replaceFirstTests =
} }
] ]
[<Tests>] [<Tests>]
let sndAsStringTests = let replaceTests =
testList "sndAsString" [ testList "String.replace" [
test "converts the second item to a string" { test "succeeds" {
Expect.equal (sndAsString ("a", 5)) "5" "The second part of the tuple should have been converted to a string" Expect.equal (replace "a" "b" "abacab") "bbbcbb" "String did not replace properly"
}
]
[<Tests>]
let trimTests =
testList "String.trim" [
test "succeeds" {
Expect.equal (trim " abc ") "abc" "Space not trimmed from string properly"
} }
] ]
@ -127,3 +147,27 @@ let wordWrapTests =
Expect.equal (wordWrap 80 testString) testString "Blank lines were not preserved" Expect.equal (wordWrap 80 testString) testString "Blank lines were not preserved"
} }
] ]
[<Tests>]
let wordWrapBTests =
testList "wordWrapB" [
test "breaks where it is supposed to" {
let testString = "The quick brown fox jumps over the lazy dog\nIt does!"
Expect.equal (wordWrap 20 testString) "The quick brown fox\njumps over the lazy\ndog\nIt does!\n"
"Line not broken correctly"
}
test "wraps long line without a space and a line with exact length" {
let testString = "Asamatteroffact, the dog does too"
Expect.equal (wordWrap 10 testString) "Asamattero\nffact, the\ndog does\ntoo\n"
"Longer line not broken correctly"
}
test "wraps long line without a space and a line with non-exact length" {
let testString = "Asamatteroffact, that dog does too"
Expect.equal (wordWrap 10 testString) "Asamattero\nffact,\nthat dog\ndoes too\n"
"Longer line not broken correctly"
}
test "preserves blank lines" {
let testString = "Here is\n\na string with blank lines"
Expect.equal (wordWrap 80 testString) testString "Blank lines were not preserved"
}
]

View File

@ -10,29 +10,39 @@ open System
/// Hash a string with a SHA1 hash /// Hash a string with a SHA1 hash
let sha1Hash (x : string) = let sha1Hash (x : string) =
use alg = SHA1.Create () use alg = SHA1.Create ()
alg.ComputeHash (ASCIIEncoding().GetBytes x) alg.ComputeHash (Encoding.ASCII.GetBytes x)
|> Seq.map (fun chr -> chr.ToString "x2") |> Seq.map (fun chr -> chr.ToString "x2")
|> Seq.reduce (+) |> String.concat ""
/// Hash a string using 1,024 rounds of PBKDF2 and a salt /// Hash a string using 1,024 rounds of PBKDF2 and a salt
let pbkdf2Hash (salt : Guid) (x : string) = let pbkdf2Hash (salt : Guid) (x : string) =
use alg = new Rfc2898DeriveBytes (x, Encoding.UTF8.GetBytes (salt.ToString "N"), 1024) use alg = new Rfc2898DeriveBytes (x, Encoding.UTF8.GetBytes (salt.ToString "N"), 1024)
Convert.ToBase64String(alg.GetBytes 64) (alg.GetBytes >> Convert.ToBase64String) 64
/// Replace the first occurrence of a string with a second string within a given string /// String helper functions
let replaceFirst (needle : string) replacement (haystack : string) = module String =
let i = haystack.IndexOf needle
match i with /// string.Trim()
let trim (str: string) = str.Trim ()
/// string.Replace()
let replace (find : string) repl (str : string) = str.Replace (find, repl)
/// Replace the first occurrence of a string with a second string within a given string
let replaceFirst (needle : string) replacement (haystack : string) =
match haystack.IndexOf needle with
| -1 -> haystack | -1 -> haystack
| _ -> | idx ->
seq { seq {
yield haystack.Substring (0, i) yield haystack.[0..idx - 1]
yield replacement yield replacement
yield haystack.Substring (i + needle.Length) yield haystack.[idx + needle.Length..]
} }
|> Seq.reduce (+) |> String.concat ""
/// Strip HTML tags from the given string /// Strip HTML tags from the given string
// Adapted from http://www.dijksterhuis.org/safely-cleaning-html-with-strip_tags-in-csharp/ // Adapted from http://www.dijksterhuis.org/safely-cleaning-html-with-strip_tags-in-csharp/
@ -51,21 +61,15 @@ let stripTags allowedTags input =
|| htmlTag.IndexOf (sprintf "</%s" t) = 0) false || htmlTag.IndexOf (sprintf "</%s" t) = 0) false
match isAllowed with match isAllowed with
| true -> () | true -> ()
| false -> output <- replaceFirst tag.Value "" output | false -> output <- String.replaceFirst tag.Value "" output
output output
/// Wrap a string at the specified number of characters /// Wrap a string at the specified number of characters
let wordWrap charPerLine (input : string) = let wordWrap charPerLine (input : string) =
match input.Length with match input.Length with
| len when len <= charPerLine -> input | len when len <= charPerLine -> input
| _ -> | _ ->
let rec findSpace (inp : string) idx =
match idx with
| 0 -> 0
| _ ->
match inp.Substring (idx, 1) with
| null | " " -> idx
| _ -> findSpace inp (idx - 1)
seq { seq {
for line in input.Replace("\r", "").Split '\n' do for line in input.Replace("\r", "").Split '\n' do
let mutable remaining = line let mutable remaining = line
@ -73,32 +77,39 @@ let wordWrap charPerLine (input : string) =
| 0 -> () | 0 -> ()
| _ -> | _ ->
while charPerLine < remaining.Length do while charPerLine < remaining.Length do
let spaceIdx = findSpace remaining charPerLine match charPerLine + 1 < remaining.Length && remaining.[charPerLine] = ' ' with
match spaceIdx with | true ->
| 0 -> // Line length is followed by a space; return [charPerLine] as a line
yield remaining.[0..charPerLine - 1]
remaining <- remaining.[charPerLine + 1..]
| false ->
match remaining.[0..charPerLine - 1].LastIndexOf ' ' with
| -1 ->
// No whitespace; just break it at [characters] // No whitespace; just break it at [characters]
yield remaining.Substring (0, charPerLine) yield remaining.[0..charPerLine - 1]
remaining <- remaining.Substring charPerLine remaining <- remaining.[charPerLine..]
| _ -> | spaceIdx ->
yield remaining.Substring (0, spaceIdx) // Break on the last space in the line
remaining <- remaining.Substring (spaceIdx + 1) yield remaining.[0..spaceIdx - 1]
match remaining.Length with remaining <- remaining.[spaceIdx + 1..]
| 0 -> () // Leftovers - yum!
| _ -> yield remaining match remaining.Length with 0 -> () | _ -> yield remaining
} }
|> Seq.fold (fun (acc : StringBuilder) line -> acc.AppendFormat ("{0}\n", line)) (StringBuilder ()) |> Seq.fold (fun (acc : StringBuilder) line -> acc.AppendFormat ("{0}\n", line)) (StringBuilder ())
|> string |> string
/// Modify the text returned by CKEditor into the format we need for request and announcement text /// Modify the text returned by CKEditor into the format we need for request and announcement text
let ckEditorToText (text : string) = let ckEditorToText (text : string) =
text let trim (str : string) = str.Trim ()
.Replace("\n\t", "") // \r [ "\n\t", ""
.Replace("&nbsp;", " ") "&nbsp;", " "
.Replace(" ", "&#xa0; ") " ", "&#xa0; "
.Replace("</p><p>", "<br><br>") // \r "</p><p>", "<br><br>"
.Replace("</p>", "") "</p>", ""
.Replace("<p>", "") "<p>", ""
.Trim() ]
|> List.fold (fun (txt : string) (x, y) -> String.replace x y txt) text
|> trim
/// Convert an HTML piece of text to plain text /// Convert an HTML piece of text to plain text
@ -106,8 +117,12 @@ let htmlToPlainText html =
match html with match html with
| null | "" -> "" | null | "" -> ""
| _ -> | _ ->
WebUtility.HtmlDecode((html.Trim() |> stripTags [ "br" ]).Replace("<br />", "\n").Replace("<br>", "\n")) html.Trim ()
.Replace("\u00a0", " ") |> stripTags [ "br" ]
|> String.replace "<br />" "\n"
|> String.replace "<br>" "\n"
|> WebUtility.HtmlDecode
|> String.replace "\u00a0" " "
/// Get the second portion of a tuple as a string /// Get the second portion of a tuple as a string
let sndAsString x = (snd >> string) x let sndAsString x = (snd >> string) x