Repository: kovetskiy/mark
Pull request #522 (2024-10-22 08:57:26)

Feat: Use custom heading anchors
Confluence Anchors are case-sensitive.


Patch:
diff --git a/markdown/markdown.go b/markdown/markdown.go
index 26f8297..50c45ba 100644
--- a/markdown/markdown.go
+++ b/markdown/markdown.go
@@ -91,8 +91,10 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, mermaidPr
 			html.WithXHTML(),
 		))
 
+	ctx := parser.NewContext(parser.WithIDs(&cparser.ConfluenceIDs{Values: map[string]bool{}}))
+
 	var buf bytes.Buffer
-	err := converter.Convert(markdown, &buf)
+	err := converter.Convert(markdown, &buf, parser.WithContext(ctx))
 
 	if err != nil {
 		panic(err)
diff --git a/parser/confluenceids.go b/parser/confluenceids.go
new file mode 100644
index 0000000..1f7acd2
--- /dev/null
+++ b/parser/confluenceids.go
@@ -0,0 +1,56 @@
+package parser
+
+import (
+	"fmt"
+
+	"github.com/yuin/goldmark/ast"
+	"github.com/yuin/goldmark/util"
+)
+
+type ConfluenceIDs struct {
+	Values map[string]bool
+}
+
+
+// https://github.com/yuin/goldmark/blob/d9c03f07f08c2d36f23afe52dda865f05320ac86/parser/parser.go#L75
+func (s *ConfluenceIDs) Generate(value []byte, kind ast.NodeKind) []byte {
+	value = util.TrimLeftSpace(value)
+	value = util.TrimRightSpace(value)
+	result := []byte{}
+	for i := 0; i < len(value); {
+		v := value[i]
+		l := util.UTF8Len(v)
+		i += int(l)
+		if l != 1 {
+			continue
+		}
+		if util.IsAlphaNumeric(v) || v == '/' || v == '_' || v == '.' {
+			result = append(result, v)
+		} else if util.IsSpace(v) || v == '-' {
+			result = append(result, '-')
+		}
+	}
+	if len(result) == 0 {
+		if kind == ast.KindHeading {
+			result = []byte("heading")
+		} else {
+			result = []byte("id")
+		}
+	}
+	if _, ok := s.Values[util.BytesToReadOnlyString(result)]; !ok {
+		s.Values[util.BytesToReadOnlyString(result)] = true
+		return result
+	}
+	for i := 1; ; i++ {
+		newResult := fmt.Sprintf("%s-%d", result, i)
+		if _, ok := s.Values[newResult]; !ok {
+			s.Values[newResult] = true
+			return []byte(newResult)
+		}
+
+	}
+}
+
+func (s *ConfluenceIDs) Put(value []byte) {
+	s.Values[util.BytesToReadOnlyString(value)] = true
+}


Test patch:
diff --git a/testdata/header-droph1.html b/testdata/header-droph1.html
index c63a140..09e3322 100644
--- a/testdata/header-droph1.html
+++ b/testdata/header-droph1.html
@@ -4,3 +4,4 @@
 <h5 id="e">e</h5>
 <h1 id="f">f</h1>
 <h2 id="g">g</h2>
+<h1 id="This/is-some_Heading.yml">This/is some_Heading.yml</h1>
diff --git a/testdata/header.html b/testdata/header.html
index 566c4ae..4424233 100644
--- a/testdata/header.html
+++ b/testdata/header.html
@@ -5,3 +5,4 @@
 <h5 id="e">e</h5>
 <h1 id="f">f</h1>
 <h2 id="g">g</h2>
+<h1 id="This/is-some_Heading.yml">This/is some_Heading.yml</h1>
diff --git a/testdata/header.md b/testdata/header.md
index 81a9738..4dbdc53 100644
--- a/testdata/header.md
+++ b/testdata/header.md
@@ -8,3 +8,5 @@ f
 =
 g
 -
+
+# This/is some_Heading.yml
diff --git a/testdata/quotes-droph1.html b/testdata/quotes-droph1.html
index 4fdba68..3d7d2de 100644
--- a/testdata/quotes-droph1.html
+++ b/testdata/quotes-droph1.html
@@ -1,4 +1,4 @@
-<h2 id="first-heading">First Heading</h2>
+<h2 id="First-Heading">First Heading</h2>
 <ac:structured-macro ac:name="note"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>NOTES:</strong></p>
 <ol>
@@ -11,7 +11,7 @@ b</p>
 </blockquote>
 <p><strong>Warn (Should not be picked as blockquote type)</strong></p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="second-heading">Second Heading</h2>
+<h2 id="Second-Heading">Second Heading</h2>
 <ac:structured-macro ac:name="warning"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>Warn</strong></p>
 <ul>
@@ -23,12 +23,12 @@ b</p>
 <li>Regular list
 that runs long</li>
 </ul>
-<h2 id="third-heading">Third Heading</h2>
+<h2 id="Third-Heading">Third Heading</h2>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <!-- Info -->
 <p>Test</p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="fourth-heading---warn-should-not-get-picked-as-block-quote">Fourth Heading - Warn should not get picked as block quote</h2>
+<h2 id="Fourth-Heading---Warn-should-not-get-picked-as-block-quote">Fourth Heading - Warn should not get picked as block quote</h2>
 <ac:structured-macro ac:name="tip"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>TIP:</strong></p>
 <ol>
@@ -41,12 +41,12 @@ b</p>
 </blockquote>
 <p><strong>Warn (Should not be picked as blockquote type)</strong></p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="simple-blockquote">Simple Blockquote</h2>
+<h2 id="Simple-Blockquote">Simple Blockquote</h2>
 <blockquote>
 <p>This paragraph is a simple blockquote</p>
 </blockquote>
-<h2 id="gh-alerts-heading">GH Alerts Heading</h2>
-<h3 id="note-type-alert-heading">Note Type Alert Heading</h3>
+<h2 id="GH-Alerts-Heading">GH Alerts Heading</h2>
+<h3 id="Note-Type-Alert-Heading">Note Type Alert Heading</h3>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!NOTE]</p>
 <ul>
@@ -54,7 +54,7 @@ b</p>
 <li>Note bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="tip-type-alert-heading">Tip Type Alert Heading</h3>
+<h3 id="Tip-Type-Alert-Heading">Tip Type Alert Heading</h3>
 <ac:structured-macro ac:name="tip"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!TIP]</p>
 <ul>
@@ -62,7 +62,7 @@ b</p>
 <li>Tip bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="warning-type-alert-heading">Warning Type Alert Heading</h3>
+<h3 id="Warning-Type-Alert-Heading">Warning Type Alert Heading</h3>
 <ac:structured-macro ac:name="note"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!WARNING]</p>
 <ul>
@@ -70,7 +70,7 @@ b</p>
 <li>Warning bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="importantcaution-type-alert-heading">Important/Caution Type Alert Heading</h3>
+<h3 id="Important/Caution-Type-Alert-Heading">Important/Caution Type Alert Heading</h3>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!IMPORTANT]</p>
 <ul>
@@ -85,7 +85,7 @@ b</p>
 <li>Important bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="should-not-be-picked-up-and-converted-into-blockquote-macro">Should not be picked up and converted into blockquote macro</h3>
+<h3 id="Should-not-be-picked-up-and-converted-into-blockquote-macro">Should not be picked up and converted into blockquote macro</h3>
 <blockquote>
 <p>[[!NOTE]</p>
 </blockquote>
diff --git a/testdata/quotes-stripnewlines.html b/testdata/quotes-stripnewlines.html
index e07a047..28e101a 100644
--- a/testdata/quotes-stripnewlines.html
+++ b/testdata/quotes-stripnewlines.html
@@ -1,5 +1,5 @@
-<h1 id="main-heading">Main Heading</h1>
-<h2 id="first-heading">First Heading</h2>
+<h1 id="Main-Heading">Main Heading</h1>
+<h2 id="First-Heading">First Heading</h2>
 <ac:structured-macro ac:name="note"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>NOTES:</strong></p>
 <ol>
@@ -11,7 +11,7 @@
 </blockquote>
 <p><strong>Warn (Should not be picked as blockquote type)</strong></p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="second-heading">Second Heading</h2>
+<h2 id="Second-Heading">Second Heading</h2>
 <ac:structured-macro ac:name="warning"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>Warn</strong></p>
 <ul>
@@ -22,12 +22,12 @@
 <ul>
 <li>Regular list that runs long</li>
 </ul>
-<h2 id="third-heading">Third Heading</h2>
+<h2 id="Third-Heading">Third Heading</h2>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <!-- Info -->
 <p>Test</p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="fourth-heading---warn-should-not-get-picked-as-block-quote">Fourth Heading - Warn should not get picked as block quote</h2>
+<h2 id="Fourth-Heading---Warn-should-not-get-picked-as-block-quote">Fourth Heading - Warn should not get picked as block quote</h2>
 <ac:structured-macro ac:name="tip"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>TIP:</strong></p>
 <ol>
@@ -39,12 +39,12 @@
 </blockquote>
 <p><strong>Warn (Should not be picked as blockquote type)</strong></p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="simple-blockquote">Simple Blockquote</h2>
+<h2 id="Simple-Blockquote">Simple Blockquote</h2>
 <blockquote>
 <p>This paragraph is a simple blockquote</p>
 </blockquote>
-<h2 id="gh-alerts-heading">GH Alerts Heading</h2>
-<h3 id="note-type-alert-heading">Note Type Alert Heading</h3>
+<h2 id="GH-Alerts-Heading">GH Alerts Heading</h2>
+<h3 id="Note-Type-Alert-Heading">Note Type Alert Heading</h3>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!NOTE]</p>
 <ul>
@@ -52,7 +52,7 @@
 <li>Note bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="tip-type-alert-heading">Tip Type Alert Heading</h3>
+<h3 id="Tip-Type-Alert-Heading">Tip Type Alert Heading</h3>
 <ac:structured-macro ac:name="tip"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!TIP]</p>
 <ul>
@@ -60,7 +60,7 @@
 <li>Tip bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="warning-type-alert-heading">Warning Type Alert Heading</h3>
+<h3 id="Warning-Type-Alert-Heading">Warning Type Alert Heading</h3>
 <ac:structured-macro ac:name="note"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!WARNING]</p>
 <ul>
@@ -68,7 +68,7 @@
 <li>Warning bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="importantcaution-type-alert-heading">Important/Caution Type Alert Heading</h3>
+<h3 id="Important/Caution-Type-Alert-Heading">Important/Caution Type Alert Heading</h3>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!IMPORTANT]</p>
 <ul>
@@ -83,7 +83,7 @@
 <li>Important bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="should-not-be-picked-up-and-converted-into-blockquote-macro">Should not be picked up and converted into blockquote macro</h3>
+<h3 id="Should-not-be-picked-up-and-converted-into-blockquote-macro">Should not be picked up and converted into blockquote macro</h3>
 <blockquote>
 <p>[[!NOTE]</p>
 </blockquote>
diff --git a/testdata/quotes.html b/testdata/quotes.html
index 15a2317..28ab52f 100644
--- a/testdata/quotes.html
+++ b/testdata/quotes.html
@@ -1,5 +1,5 @@
-<h1 id="main-heading">Main Heading</h1>
-<h2 id="first-heading">First Heading</h2>
+<h1 id="Main-Heading">Main Heading</h1>
+<h2 id="First-Heading">First Heading</h2>
 <ac:structured-macro ac:name="note"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>NOTES:</strong></p>
 <ol>
@@ -12,7 +12,7 @@ b</p>
 </blockquote>
 <p><strong>Warn (Should not be picked as blockquote type)</strong></p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="second-heading">Second Heading</h2>
+<h2 id="Second-Heading">Second Heading</h2>
 <ac:structured-macro ac:name="warning"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>Warn</strong></p>
 <ul>
@@ -24,12 +24,12 @@ b</p>
 <li>Regular list
 that runs long</li>
 </ul>
-<h2 id="third-heading">Third Heading</h2>
+<h2 id="Third-Heading">Third Heading</h2>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <!-- Info -->
 <p>Test</p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="fourth-heading---warn-should-not-get-picked-as-block-quote">Fourth Heading - Warn should not get picked as block quote</h2>
+<h2 id="Fourth-Heading---Warn-should-not-get-picked-as-block-quote">Fourth Heading - Warn should not get picked as block quote</h2>
 <ac:structured-macro ac:name="tip"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p><strong>TIP:</strong></p>
 <ol>
@@ -42,12 +42,12 @@ b</p>
 </blockquote>
 <p><strong>Warn (Should not be picked as blockquote type)</strong></p>
 </ac:rich-text-body></ac:structured-macro>
-<h2 id="simple-blockquote">Simple Blockquote</h2>
+<h2 id="Simple-Blockquote">Simple Blockquote</h2>
 <blockquote>
 <p>This paragraph is a simple blockquote</p>
 </blockquote>
-<h2 id="gh-alerts-heading">GH Alerts Heading</h2>
-<h3 id="note-type-alert-heading">Note Type Alert Heading</h3>
+<h2 id="GH-Alerts-Heading">GH Alerts Heading</h2>
+<h3 id="Note-Type-Alert-Heading">Note Type Alert Heading</h3>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!NOTE]</p>
 <ul>
@@ -55,7 +55,7 @@ b</p>
 <li>Note bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="tip-type-alert-heading">Tip Type Alert Heading</h3>
+<h3 id="Tip-Type-Alert-Heading">Tip Type Alert Heading</h3>
 <ac:structured-macro ac:name="tip"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!TIP]</p>
 <ul>
@@ -63,7 +63,7 @@ b</p>
 <li>Tip bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="warning-type-alert-heading">Warning Type Alert Heading</h3>
+<h3 id="Warning-Type-Alert-Heading">Warning Type Alert Heading</h3>
 <ac:structured-macro ac:name="note"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!WARNING]</p>
 <ul>
@@ -71,7 +71,7 @@ b</p>
 <li>Warning bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="importantcaution-type-alert-heading">Important/Caution Type Alert Heading</h3>
+<h3 id="Important/Caution-Type-Alert-Heading">Important/Caution Type Alert Heading</h3>
 <ac:structured-macro ac:name="info"><ac:parameter ac:name="icon">true</ac:parameter><ac:rich-text-body>
 <p>[!IMPORTANT]</p>
 <ul>
@@ -86,7 +86,7 @@ b</p>
 <li>Important bullet 2</li>
 </ul>
 </ac:rich-text-body></ac:structured-macro>
-<h3 id="should-not-be-picked-up-and-converted-into-blockquote-macro">Should not be picked up and converted into blockquote macro</h3>
+<h3 id="Should-not-be-picked-up-and-converted-into-blockquote-macro">Should not be picked up and converted into blockquote macro</h3>
 <blockquote>
 <p>[[!NOTE]</p>
 </blockquote>