diff --git a/CHANGES.md b/CHANGES.md
index 07f60a10..cee783ed 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -24,6 +24,7 @@
- [pull #734] Fix empty list items being parsed as nested lists (#167)
- [pull #735] Fix `smarty-pants` extra converting quotes inside inline HTML tags and comments (#150)
- [pull #737] Fix XSS from code spans that the span gamut doesn't treat as code spans in safe mode (#726)
+- [pull #743] Add a `strip-syntax` option to the `header-ids` extra that leaves link URLs, image paths, inline HTML tags and emphasis markers out of header ids (#192)
- [pull #747] Fix `link-patterns` extra linking text inside Markdown links and images whose label wraps onto another line
diff --git a/lib/markdown2.py b/lib/markdown2.py
index b773ebb7..3632a54e 100755
--- a/lib/markdown2.py
+++ b/lib/markdown2.py
@@ -56,7 +56,9 @@
* footnotes: Support footnotes as in use on daringfireball.net and
implemented in other Markdown processors (tho not in Markdown.pl v1.0.1).
* header-ids: Adds "id" attributes to headers. The id value is a slug of
- the header text.
+ the header text. With `{"strip-syntax": True}`, the slug is made from the
+ rendered header, so link URLs, image paths, inline HTML tags and emphasis
+ markers are left out of it.
* highlightjs-lang: Allows specifying the language which used for syntax
highlighting when using fenced-code-blocks and highlightjs.
* html-classes: Takes a dict mapping html tag names (lowercase) to a
@@ -1667,6 +1669,8 @@ def _do_links(self, text: str) -> str:
text = link_processor.run(text)
return text
+ _html_tag_re = re.compile(r'<[^>]*>')
+
def header_id_from_text(self,
text: str,
prefix: str,
@@ -1751,16 +1755,26 @@ def _h_sub(self, match: re.Match[str]) -> str:
if demote_headers:
n = min(n + demote_headers, 6)
header_id_attr = ""
+ html = self._run_span_gamut(header_group)
if "header-ids" in self.extras:
# in safe mode, inline HTML is hashed by now. Unhash it so the
# sanitized HTML doesn't get swapped back into the id attribute
- header_text = self._unhash_html_spans(header_group)
+ if self.extras["header-ids"].get('strip-syntax'):
+ # build the id from the rendered header so that markdown syntax
+ # (link URLs, image paths, emphasis markers) doesn't end up in it.
+ header_text = self._html_tag_re.sub('', self._unhash_html_spans(html))
+ if "footnotes" in self.extras:
+ # footnote refs are numbered later on. Drop the placeholder
+ # so it doesn't end up in the id
+ marker = self._footnote_marker + '-'
+ header_text = header_text.replace(marker, '')
+ else:
+ header_text = self._unhash_html_spans(header_group)
header_id = self.header_id_from_text(
self._unescape_special_chars(header_text),
self.extras["header-ids"].get('prefix'), n)
if header_id:
header_id_attr = ' id="%s"' % header_id
- html = self._run_span_gamut(header_group)
if "toc" in self.extras and header_id:
self._toc_add_entry(n, header_id, html)
return "%s\n\n" % (n, header_id_attr, html, n)
diff --git a/test/tm-cases/header_ids_footnote_ref.html b/test/tm-cases/header_ids_footnote_ref.html
new file mode 100644
index 00000000..1489d32f
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref.html
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/test/tm-cases/header_ids_footnote_ref.opts b/test/tm-cases/header_ids_footnote_ref.opts
new file mode 100644
index 00000000..fa2d42bf
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref.opts
@@ -0,0 +1 @@
+{"extras": {"header-ids": {"strip-syntax": True}, "footnotes": None}}
diff --git a/test/tm-cases/header_ids_footnote_ref.tags b/test/tm-cases/header_ids_footnote_ref.tags
new file mode 100644
index 00000000..51fe5df8
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref.tags
@@ -0,0 +1 @@
+extra header-ids footnotes issue192
diff --git a/test/tm-cases/header_ids_footnote_ref.text b/test/tm-cases/header_ids_footnote_ref.text
new file mode 100644
index 00000000..39a50b8c
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref.text
@@ -0,0 +1,6 @@
+# Header with a footnote[^1]
+
+# [Linked](http://example.com) header[^note]
+
+[^1]: The first note.
+[^note]: The second note.
diff --git a/test/tm-cases/header_ids_footnote_ref_default.html b/test/tm-cases/header_ids_footnote_ref_default.html
new file mode 100644
index 00000000..7c15fb22
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref_default.html
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/test/tm-cases/header_ids_footnote_ref_default.opts b/test/tm-cases/header_ids_footnote_ref_default.opts
new file mode 100644
index 00000000..00b84fb2
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref_default.opts
@@ -0,0 +1 @@
+{"extras": ["header-ids", "footnotes"]}
diff --git a/test/tm-cases/header_ids_footnote_ref_default.tags b/test/tm-cases/header_ids_footnote_ref_default.tags
new file mode 100644
index 00000000..51fe5df8
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref_default.tags
@@ -0,0 +1 @@
+extra header-ids footnotes issue192
diff --git a/test/tm-cases/header_ids_footnote_ref_default.text b/test/tm-cases/header_ids_footnote_ref_default.text
new file mode 100644
index 00000000..39a50b8c
--- /dev/null
+++ b/test/tm-cases/header_ids_footnote_ref_default.text
@@ -0,0 +1,6 @@
+# Header with a footnote[^1]
+
+# [Linked](http://example.com) header[^note]
+
+[^1]: The first note.
+[^note]: The second note.
diff --git a/test/tm-cases/header_ids_issue192.html b/test/tm-cases/header_ids_issue192.html
new file mode 100644
index 00000000..bec35661
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192.html
@@ -0,0 +1,13 @@
+
+
+
+
+
Image
+
+
+
+Inline HTML
+
+Strong and em
+
+code_span stays
diff --git a/test/tm-cases/header_ids_issue192.opts b/test/tm-cases/header_ids_issue192.opts
new file mode 100644
index 00000000..b71e6519
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192.opts
@@ -0,0 +1 @@
+{"extras": {"header-ids": {"strip-syntax": True}}}
diff --git a/test/tm-cases/header_ids_issue192.tags b/test/tm-cases/header_ids_issue192.tags
new file mode 100644
index 00000000..d01b19bc
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192.tags
@@ -0,0 +1 @@
+extra header-ids issue192
diff --git a/test/tm-cases/header_ids_issue192.text b/test/tm-cases/header_ids_issue192.text
new file mode 100644
index 00000000..4ab848ae
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192.text
@@ -0,0 +1,15 @@
+# [Inline link](https://github.com)
+
+# [Reference link][ref]
+
+#  Image
+
+# **[Bold link](http://example.com "Title")**
+
+# Inline HTML
+
+# __Strong__ and _em_
+
+# `code_span` stays
+
+[ref]: http://example.com
diff --git a/test/tm-cases/header_ids_issue192_default.html b/test/tm-cases/header_ids_issue192_default.html
new file mode 100644
index 00000000..20c66497
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192_default.html
@@ -0,0 +1,13 @@
+
+
+
+
+
Image
+
+
+
+Inline HTML
+
+Strong and em
+
+code_span stays
diff --git a/test/tm-cases/header_ids_issue192_default.opts b/test/tm-cases/header_ids_issue192_default.opts
new file mode 100644
index 00000000..38b8bfe8
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192_default.opts
@@ -0,0 +1 @@
+{"extras": ["header-ids"]}
diff --git a/test/tm-cases/header_ids_issue192_default.tags b/test/tm-cases/header_ids_issue192_default.tags
new file mode 100644
index 00000000..d01b19bc
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192_default.tags
@@ -0,0 +1 @@
+extra header-ids issue192
diff --git a/test/tm-cases/header_ids_issue192_default.text b/test/tm-cases/header_ids_issue192_default.text
new file mode 100644
index 00000000..4ab848ae
--- /dev/null
+++ b/test/tm-cases/header_ids_issue192_default.text
@@ -0,0 +1,15 @@
+# [Inline link](https://github.com)
+
+# [Reference link][ref]
+
+#  Image
+
+# **[Bold link](http://example.com "Title")**
+
+# Inline HTML
+
+# __Strong__ and _em_
+
+# `code_span` stays
+
+[ref]: http://example.com