Skip to content

Commit 08d9632

Browse files
committed
Fix duplicate heading id
1 parent 27145cc commit 08d9632

File tree

7 files changed

+72
-27
lines changed

7 files changed

+72
-27
lines changed

lib/marked.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,13 +953,13 @@ Renderer.prototype.html = function(html) {
953953
return html;
954954
};
955955

956-
Renderer.prototype.heading = function(text, level, raw) {
956+
Renderer.prototype.heading = function(text, level, raw, slugger) {
957957
if (this.options.headerIds) {
958958
return '<h'
959959
+ level
960960
+ ' id="'
961961
+ this.options.headerPrefix
962-
+ raw.toLowerCase().replace(/[^\w]+/g, '-')
962+
+ slugger.slug(raw)
963963
+ '">'
964964
+ text
965965
+ '</h'
@@ -1108,6 +1108,7 @@ function Parser(options) {
11081108
this.options.renderer = this.options.renderer || new Renderer();
11091109
this.renderer = this.options.renderer;
11101110
this.renderer.options = this.options;
1111+
this.slugger = new Slugger();
11111112
}
11121113

11131114
/**
@@ -1186,7 +1187,8 @@ Parser.prototype.tok = function() {
11861187
return this.renderer.heading(
11871188
this.inline.output(this.token.text),
11881189
this.token.depth,
1189-
unescape(this.inlineText.output(this.token.text)));
1190+
unescape(this.inlineText.output(this.token.text)),
1191+
this.slugger);
11901192
}
11911193
case 'code': {
11921194
return this.renderer.code(this.token.text,
@@ -1283,6 +1285,35 @@ Parser.prototype.tok = function() {
12831285
}
12841286
};
12851287

1288+
/**
1289+
* Slugger generates header id
1290+
*/
1291+
1292+
function Slugger () {
1293+
this.seen = {};
1294+
}
1295+
1296+
/**
1297+
* Convert string to unique id
1298+
*/
1299+
1300+
Slugger.prototype.slug = function (value) {
1301+
var slug = value
1302+
.toLowerCase()
1303+
.trim()
1304+
.replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '')
1305+
.replace(/\s/g, '-');
1306+
1307+
var count = this.seen.hasOwnProperty(slug) ? this.seen[slug] + 1 : 0;
1308+
this.seen[slug] = count;
1309+
1310+
if (count > 0) {
1311+
slug = slug + '-' + count;
1312+
}
1313+
1314+
return slug;
1315+
};
1316+
12861317
/**
12871318
* Helpers
12881319
*/

test/new/cm_blockquotes.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ <h3 id="example-192">Example 192</h3>
1111
<p>The spaces after the <code>&gt;</code> characters can be omitted:</p>
1212

1313
<blockquote>
14-
<h1 id="foo">Foo</h1>
14+
<h1 id="bar">Bar</h1>
1515
<p>bar
1616
baz</p>
1717
</blockquote>
@@ -21,7 +21,7 @@ <h3 id="example-193">Example 193</h3>
2121
<p>The <code>&gt;</code> characters can be indented 1-3 spaces:</p>
2222

2323
<blockquote>
24-
<h1 id="foo">Foo</h1>
24+
<h1 id="baz">Baz</h1>
2525
<p>bar
2626
baz</p>
2727
</blockquote>
@@ -30,7 +30,7 @@ <h3 id="example-194">Example 194</h3>
3030

3131
<p>Four spaces gives us a code block:</p>
3232

33-
<pre><code>&gt; # Foo
33+
<pre><code>&gt; # Qux
3434
&gt; bar
3535
&gt; baz</code></pre>
3636

@@ -39,7 +39,7 @@ <h3 id="example-195">Example 195</h3>
3939
<p>The Laziness clause allows us to omit the <code>&gt;</code> before paragraph continuation text:</p>
4040

4141
<blockquote>
42-
<h1 id="foo">Foo</h1>
42+
<h1 id="quux">Quux</h1>
4343
<p>bar
4444
baz</p>
4545
</blockquote>

test/new/cm_blockquotes.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@
88

99
The spaces after the `>` characters can be omitted:
1010

11-
># Foo
11+
># Bar
1212
>bar
1313
> baz
1414
1515
### Example 193
1616

1717
The `>` characters can be indented 1-3 spaces:
1818

19-
> # Foo
19+
> # Baz
2020
> bar
2121
> baz
2222
2323
### Example 194
2424

2525
Four spaces gives us a code block:
2626

27-
> # Foo
27+
> # Qux
2828
> bar
2929
> baz
3030

3131
### Example 195
3232

3333
The Laziness clause allows us to omit the `>` before paragraph continuation text:
3434

35-
> # Foo
35+
> # Quux
3636
> bar
3737
baz
3838

test/new/toplevel_paragraphs.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<h1 id="how-are-you">how are you</h1>
1313

1414
<p>paragraph before head with equals</p>
15-
<h1 id="how-are-you">how are you</h1>
15+
<h1 id="how-are-you-again">how are you again</h1>
1616

1717
<p>paragraph before blockquote</p>
1818
<blockquote><p>text for blockquote</p></blockquote>

test/new/toplevel_paragraphs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ paragraph before head with hash
1717
# how are you
1818

1919
paragraph before head with equals
20-
how are you
20+
how are you again
2121
===========
2222

2323
paragraph before blockquote

test/original/markdown_documentation_basics.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h1>Markdown: Basics</h1>
1+
<h1 id="markdown-basics">Markdown: Basics</h1>
22

33
<ul id="ProjectSubmenu">
44
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
@@ -8,7 +8,7 @@ <h1>Markdown: Basics</h1>
88
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
99
</ul>
1010

11-
<h2>Getting the Gist of Markdown's Formatting Syntax</h2>
11+
<h2 id="getting-the-gist-of-markdowns-formatting-syntax">Getting the Gist of Markdown's Formatting Syntax</h2>
1212

1313
<p>This page offers a brief overview of what it's like to use Markdown.
1414
The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for
@@ -24,7 +24,7 @@ <h2>Getting the Gist of Markdown's Formatting Syntax</h2>
2424
<p><strong>Note:</strong> This document is itself written using Markdown; you
2525
can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p>
2626

27-
<h2>Paragraphs, Headers, Blockquotes</h2>
27+
<h2 id="paragraphs-headers-blockquotes">Paragraphs, Headers, Blockquotes</h2>
2828

2929
<p>A paragraph is simply one or more consecutive lines of text, separated
3030
by one or more blank lines. (A blank line is any line that looks like a
@@ -88,7 +88,7 @@ <h2>Paragraphs, Headers, Blockquotes</h2>
8888
&lt;/blockquote&gt;
8989
</code></pre>
9090

91-
<h3>Phrase Emphasis</h3>
91+
<h3 id="phrase-emphasis">Phrase Emphasis</h3>
9292

9393
<p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p>
9494

@@ -110,7 +110,7 @@ <h3>Phrase Emphasis</h3>
110110
Or, if you prefer, &lt;strong&gt;use two underscores instead&lt;/strong&gt;.&lt;/p&gt;
111111
</code></pre>
112112

113-
<h2>Lists</h2>
113+
<h2 id="lists">Lists</h2>
114114

115115
<p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>,
116116
<code>+</code>, and <code>-</code>) as list markers. These three markers are
@@ -181,7 +181,7 @@ <h2>Lists</h2>
181181
&lt;/ul&gt;
182182
</code></pre>
183183

184-
<h3>Links</h3>
184+
<h3 id="links">Links</h3>
185185

186186
<p>Markdown supports two styles for creating links: <em>inline</em> and
187187
<em>reference</em>. With both styles, you use square brackets to delimit the
@@ -244,7 +244,7 @@ <h3>Links</h3>
244244
&lt;a href="http://www.nytimes.com/"&gt;The New York Times&lt;/a&gt;.&lt;/p&gt;
245245
</code></pre>
246246

247-
<h3>Images</h3>
247+
<h3 id="images">Images</h3>
248248

249249
<p>Image syntax is very much like link syntax.</p>
250250

@@ -265,7 +265,7 @@ <h3>Images</h3>
265265
<pre><code>&lt;img src="/path/to/img.jpg" alt="alt text" title="Title" /&gt;
266266
</code></pre>
267267

268-
<h3>Code</h3>
268+
<h3 id="code">Code</h3>
269269

270270
<p>In a regular paragraph, you can create code span by wrapping text in
271271
backtick quotes. Any ampersands (<code>&amp;</code>) and angle brackets (<code>&lt;</code> or

test/unit/marked-spec.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,29 @@ var marked = require('../../lib/marked.js');
22

33
describe('Test heading ID functionality', function() {
44
it('should add id attribute by default', function() {
5-
var renderer = new marked.Renderer(marked.defaults);
6-
var header = renderer.heading('test', 1, 'test');
7-
expect(header).toBe('<h1 id="test">test</h1>\n');
5+
var html = marked('# test');
6+
expect(html).toBe('<h1 id="test">test</h1>\n');
7+
});
8+
9+
it('should add unique id for repeating heading 1280', function() {
10+
var html = marked('# test\n# test\n# test');
11+
expect(html).toBe('<h1 id="test">test</h1>\n<h1 id="test-1">test</h1>\n<h1 id="test-2">test</h1>\n');
12+
});
13+
14+
it('should add id with non-latin chars', function() {
15+
var html = marked('# привет');
16+
expect(html).toBe('<h1 id="привет">привет</h1>\n');
17+
});
18+
19+
it('should add id without ampersands 857', function() {
20+
var html = marked('# This & That Section');
21+
expect(html).toBe('<h1 id="this--that-section">This &amp; That Section</h1>\n');
822
});
923

1024
it('should NOT add id attribute when options set false', function() {
11-
var renderer = new marked.Renderer({ headerIds: false });
12-
var header = renderer.heading('test', 1, 'test');
13-
expect(header).toBe('<h1>test</h1>\n');
25+
var options = { headerIds: false };
26+
var html = marked('# test', options);
27+
expect(html).toBe('<h1>test</h1>\n');
1428
});
1529
});
1630

0 commit comments

Comments
 (0)