From 80a87b1fa4201a1e3d3e315f0edfcd2378e1fbcf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 00:28:54 +0000 Subject: [PATCH] Fix HtmlNode.ToString: preserve whitespace in elements nested inside

Elements nested multiple levels deep inside a 
 block could have
newlines and indentation inserted between them during serialization.
This corrupted output from syntax highlighters (e.g. shiki) that emit
...
. Root cause: the serialize function did not propagate an 'insidePre' context to descendant elements. When a non-pre element with multiple element children (onlyText = false) appeared inside
, the
canAddNewLine flag on sibling elements caused newLines to be inserted.

Fix: add an 'insidePre' parameter to serialize. When true, all
newline/indentation formatting is suppressed regardless of element type.

Closes #1509

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 src/FSharp.Data.Html.Core/HtmlNode.fs      | 13 +++++++------
 tests/FSharp.Data.Core.Tests/HtmlParser.fs | 12 ++++++++++++
 2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/FSharp.Data.Html.Core/HtmlNode.fs b/src/FSharp.Data.Html.Core/HtmlNode.fs
index fa0f6c245..13485c6bb 100644
--- a/src/FSharp.Data.Html.Core/HtmlNode.fs
+++ b/src/FSharp.Data.Html.Core/HtmlNode.fs
@@ -114,7 +114,7 @@ type HtmlNode =
 
             fun name -> Set.contains name set
 
-        let rec serialize (sb: StringBuilder) indentation canAddNewLine html =
+        let rec serialize (sb: StringBuilder) indentation canAddNewLine insidePre html =
             let append (str: string) = sb.Append str |> ignore
 
             let appendEndTag name =
@@ -135,8 +135,9 @@ type HtmlNode =
                         | _ -> false)
 
                 let isPreTag = name = "pre"
+                let nowInsidePre = insidePre || isPreTag
 
-                if canAddNewLine && not (onlyText || isPreTag) then
+                if canAddNewLine && not insidePre && not (onlyText || isPreTag) then
                     newLine 0
 
                 append "<"
@@ -157,16 +158,16 @@ type HtmlNode =
                 else
                     append ">"
 
-                    if not (onlyText || isPreTag) then
+                    if not insidePre && not (onlyText || isPreTag) then
                         newLine 2
 
                     let mutable canAddNewLine = false
 
                     for element in elements do
-                        serialize sb (indentation + 2) canAddNewLine element
+                        serialize sb (indentation + 2) canAddNewLine nowInsidePre element
                         canAddNewLine <- true
 
-                    if not (onlyText || isPreTag) then
+                    if not insidePre && not (onlyText || isPreTag) then
                         newLine 0
 
                     appendEndTag name
@@ -181,7 +182,7 @@ type HtmlNode =
                 append "]]>"
 
         let sb = StringBuilder()
-        serialize sb 0 false x |> ignore
+        serialize sb 0 false false x |> ignore
         sb.ToString()
 
     /// 
diff --git a/tests/FSharp.Data.Core.Tests/HtmlParser.fs b/tests/FSharp.Data.Core.Tests/HtmlParser.fs
index 392628f2a..1f1a682ed 100644
--- a/tests/FSharp.Data.Core.Tests/HtmlParser.fs
+++ b/tests/FSharp.Data.Core.Tests/HtmlParser.fs
@@ -869,6 +869,18 @@ let ``Maintain whitespace inside pre tag through round-trip``() =
     let expected = html
     result |> should equal expected
 
+[]
+let ``Maintain whitespace in deeply nested elements inside pre through round-trip``() =
+    // Regression test for https://github.com/fsprojects/FSharp.Data/issues/1509
+    // Syntax highlighters (e.g. shiki) emit 
...
+ // Without the insidePre fix, the nested multi-element spans would have newlines inserted between them. + let html = """
let x = 1
""" + + let result = HtmlDocument.Parse(html).ToString() + + let expected = html + result |> should equal expected + [] let ``Can parse national rail mobile site correctly``() = HtmlDocument.Load "UKDepartures.html"