From 67a113e1b9771b573424adcea26f2d0a8e16de4d Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 9 Apr 2026 04:39:07 +0000
Subject: [PATCH 1/2] perf: avoid ToCharArray allocations in TextConversions,
HtmlParser, HtmlCssSelectors, HtmlOperations
- TextConversions.RemoveAdorners: replace ToCharArray()+Array.filter with a
StringBuilder loop; avoids two intermediate char[] allocations (ToCharArray
result + filtered result) on the hot path for every number/date parse attempt
- HtmlParser: replace ToCharArray()|>Array.rev string reversal with Array.init
using direct index arithmetic; avoids ToCharArray allocation for malformed
end-tag detection (e.g.
)
- HtmlCssSelectors.StartsWith: remove ToCharArray(); compare list against string
directly via Seq.compareWith (string is IEnumerable)
- HtmlCssSelectors.TokenStr: replace ToCharArray()|>Array.toList with List.ofSeq
- HtmlCssSelectors.Tokenize: replace ToCharArray()|>Array.toList with List.ofSeq
- HtmlOperations AttributeContainsPrefix: remove ToCharArray() before Seq.skipWhile
since string is already IEnumerable
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/FSharp.Data.Html.Core/HtmlCssSelectors.fs | 12 +++++-------
src/FSharp.Data.Html.Core/HtmlOperations.fs | 2 +-
src/FSharp.Data.Html.Core/HtmlParser.fs | 5 ++++-
.../TextConversions.fs | 13 ++++++++-----
4 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs b/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs
index ff8e9cead..13bb0db24 100644
--- a/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs
+++ b/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs
@@ -83,20 +83,18 @@ module internal HtmlCssSelectors =
| c -> failwithf "Invalid css selector syntax at: %s" (new String(Array.ofList c))
let (|StartsWith|_|) (s: string) (items: char list) =
- let candidates = s.ToCharArray()
-
- if items.Length < candidates.Length then
+ if items.Length < s.Length then
None
else
- let start = items |> Seq.take (candidates.Length) |> Seq.toList
+ let start = items |> Seq.take s.Length
- if (Seq.compareWith (fun a b -> (int a) - (int b)) start candidates) = 0 then
+ if (Seq.compareWith (fun a b -> (int a) - (int b)) start s) = 0 then
Some(items |> Seq.skip s.Length |> Seq.toList)
else
None
let (|TokenStr|_|) (s: string) x =
- let chars = s.ToCharArray() |> Array.toList
+ let chars = List.ofSeq s
let rec equal x s =
match x, s with
@@ -208,7 +206,7 @@ module internal HtmlCssSelectors =
member public x.Tokenize(pCssSelector: string) =
cssSelector <- pCssSelector
- source <- cssSelector.ToCharArray() |> Array.toList
+ source <- List.ofSeq cssSelector
charCount <- source.Length
tokenize ()
diff --git a/src/FSharp.Data.Html.Core/HtmlOperations.fs b/src/FSharp.Data.Html.Core/HtmlOperations.fs
index 7a172ca33..4f6fb3246 100644
--- a/src/FSharp.Data.Html.Core/HtmlOperations.fs
+++ b/src/FSharp.Data.Html.Core/HtmlOperations.fs
@@ -367,7 +367,7 @@ module HtmlNode =
let selectedNodes =
filterByAttr level acc name (fun v ->
let chars =
- v.ToCharArray()
+ v
|> Seq.skipWhile (fun c -> c = '\'')
|> Seq.takeWhile Char.IsLetter
|> Seq.toArray
diff --git a/src/FSharp.Data.Html.Core/HtmlParser.fs b/src/FSharp.Data.Html.Core/HtmlParser.fs
index 011f2986d..2c83a391d 100644
--- a/src/FSharp.Data.Html.Core/HtmlParser.fs
+++ b/src/FSharp.Data.Html.Core/HtmlParser.fs
@@ -1113,7 +1113,10 @@ module internal HtmlParser =
parse' docType elements expectedTagEnd parentTagName (TagEnd expectedTagEnd :: tokens)
| TagEnd name :: rest when
name <> expectedTagEnd
- && (name <> (new String(expectedTagEnd.ToCharArray() |> Array.rev)))
+ && (name
+ <> (new String(
+ Array.init expectedTagEnd.Length (fun i -> expectedTagEnd.[expectedTagEnd.Length - 1 - i])
+ )))
->
// ignore this token if not the expected end tag (or it's reverse, eg: )
parse' docType elements expectedTagEnd parentTagName rest
diff --git a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs
index 9ff5d65ed..798b0b8e5 100644
--- a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs
+++ b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs
@@ -104,11 +104,14 @@ type TextConversions private () =
// No adorners found, return original string to avoid allocation
value
else
- // Adorners found, perform filtering
- String(
- value.ToCharArray()
- |> Array.filter (not << TextConversions.DefaultRemovableAdornerCharacters.Contains)
- )
+ // Adorners found, perform filtering via StringBuilder to avoid ToCharArray allocation
+ let sb = System.Text.StringBuilder(value.Length)
+
+ for c in value do
+ if not (TextConversions.DefaultRemovableAdornerCharacters.Contains(c)) then
+ sb.Append(c) |> ignore
+
+ sb.ToString()
/// Turns empty or null string value into None, otherwise returns Some
static member AsString str =
From 76236007886df6860ad4ccc394aed18283d12724 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Thu, 9 Apr 2026 04:39:09 +0000
Subject: [PATCH 2/2] ci: trigger checks