Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/commonMain/kotlin/ch/derlin/bitdowntoc/BitGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ object BitGenerator {
val codeMarker = listOf('`', '~').firstNotNullOfOrNull { line.getCodeStart(it) }

if (!codeMarker.isNullOrBlank()) iter.consumeCode(codeMarker)
else if (line.isCompleteHtmlComment()) {
// Single-line HTML comment, keep in output but don't process for TOC
}
else if (line.isHtmlCommentStart()) iter.consumeHtmlComment()
else if (commenter.isAnchor(line)) iter.remove()
else {
line.parseHeader(toc)?.let {
Expand Down Expand Up @@ -122,10 +126,25 @@ object BitGenerator {
.takeIf { it.startsWith("$char".repeat(3)) }
?.let { trimmedLine -> trimmedLine.takeWhile { it == char } }

private fun String.isHtmlCommentStart(): Boolean {
val trimmed = this.trim()
return trimmed.startsWith("<!--") && !trimmed.contains("TOC")
}

private fun String.isCompleteHtmlComment(): Boolean {
val trimmed = this.trim()
return trimmed.startsWith("<!--") && trimmed.endsWith("-->") && !trimmed.contains("TOC")
}

private fun Iterator<String>.consumeCode(codeMarker: String) {
while (this.hasNext() && !this.next().trim().startsWith(codeMarker));
}

private fun Iterator<String>.consumeHtmlComment() {
while (this.hasNext()) {
if (this.next().trim().endsWith("-->")) break
}
}

private fun Iterable<String>.asText() = this.joinToString(NL)
}
174 changes: 174 additions & 0 deletions src/commonTest/kotlin/ch.derlin.bitdowntoc/HtmlCommentsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package ch.derlin.bitdowntoc

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFalse

class HtmlCommentsTest {

@Test
fun testHtmlCommentsAreIgnored() {
val input = """
# Example

[TOC]

## Section A

TODO

<!--
### Placeholder SubSection
-->

## Section B

TODO
""".trimIndent()

val expected = """
# Example

<!-- TOC start (generated with $BITDOWNTOC_URL) -->

- [Section A](#section-a)
- [Section B](#section-b)

<!-- TOC end -->

## Section A

TODO

<!--
### Placeholder SubSection
-->

## Section B

TODO
""".trimIndent()

assertEquals(
expected,
BitGenerator.generate(input, BitGenerator.Params(generateAnchors = false))
)
}

@Test
fun testSingleLineHtmlCommentsAreIgnored() {
val input = """
# Example

[TOC]

## Section A

<!-- ### Commented out heading -->

## Section B
""".trimIndent()

val expected = """
# Example

<!-- TOC start (generated with $BITDOWNTOC_URL) -->

- [Section A](#section-a)
- [Section B](#section-b)

<!-- TOC end -->

## Section A

<!-- ### Commented out heading -->

## Section B
""".trimIndent()

assertEquals(
expected,
BitGenerator.generate(input, BitGenerator.Params(generateAnchors = false))
)
}

@Test
fun testMultipleHeadingsInHtmlCommentsAreIgnored() {
val input = """
# Example

[TOC]

## Section A

<!--
### First commented heading
#### Second commented heading
##### Third commented heading
-->

## Section B
""".trimIndent()

val expected = """
# Example

<!-- TOC start (generated with $BITDOWNTOC_URL) -->

- [Section A](#section-a)
- [Section B](#section-b)

<!-- TOC end -->

## Section A

<!--
### First commented heading
#### Second commented heading
##### Third commented heading
-->

## Section B
""".trimIndent()

assertEquals(
expected,
BitGenerator.generate(input, BitGenerator.Params(generateAnchors = false))
)
}

@Test
fun testBitDownTocCommentsAreNotIgnored() {
val input = """
# Example

[TOC]

## Section A

<!--
### Placeholder SubSection
-->

## Section B
""".trimIndent()

val result = BitGenerator.generate(input, BitGenerator.Params(generateAnchors = true))

// Check that HTML comments are preserved in output
assertTrue(result.contains("<!--"), "HTML comments should be preserved in output")
assertTrue(result.contains("### Placeholder SubSection"), "HTML comment content should be preserved in output")
assertTrue(result.contains("-->"), "HTML comments should be preserved in output")

// Check that TOC only contains Section A and Section B, not Placeholder SubSection
val tocLines = result.lines().dropWhile { !it.contains("TOC start") }.takeWhile { !it.contains("TOC end") }
val tocContent = tocLines.joinToString("\n")
assertTrue(tocContent.contains("Section A"), "TOC should contain Section A")
assertTrue(tocContent.contains("Section B"), "TOC should contain Section B")
assertFalse(tocContent.contains("Placeholder SubSection"), "TOC should NOT contain Placeholder SubSection")

// Check that BitDownToc comments are preserved
assertTrue(result.contains("<!-- TOC -->"), "BitDownToc comments should be preserved")
}
}