We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SEO et signature : <a rel="nofollow" />
Dans la signature il faudrait voir si on peut facilement ajouter un attribut `rel="nofollow"` pour préserver notre SEO. https://github.com/zestedesavoir/zmarkdown/blob/1dded309a2670689a4a3353f9e38b80624c6df1a/packages/zmarkdown/server/handlers.js#L139

> limitez les liens en signatures à des no follow or lien interne.
c’est pas mal (:evil) de partager un lien, mais si A-312 répond 4 fois dans la même page, il renvoie 4 fois du jus sur son compte twitter, 4 coding game, … ca a plusieurs effet négatifs
Source: https://zestedesavoir.com/forums/sujet/12099/seo-et-spam/?page=1#p199005



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zds/utils/templatetags/emarkdown.py`
Content:
```
1 import re
2 import json
3 import logging
4 from requests import post, HTTPError
5 
6 from django import template
7 from django.conf import settings
8 from django.template.defaultfilters import stringfilter
9 from django.utils.safestring import mark_safe
10 from django.utils.translation import ugettext_lazy as _
11 
12 logger = logging.getLogger(__name__)
13 register = template.Library()
14 """
15 Markdown related filters.
16 """
17 
18 # Constants
19 MAX_ATTEMPTS = 3
20 MD_PARSING_ERROR = _('Une erreur est survenue dans la génération de texte Markdown. Veuillez rapporter le bug.')
21 
22 FORMAT_ENDPOINTS = {
23     'html': '/html',
24     'texfile': '/latex-document',
25     'epub': '/epub',
26     'tex': '/latex',
27 }
28 
29 
30 def _render_markdown_once(md_input, *, output_format='html', **kwargs):
31     """
32     Returns None on error (error details are logged). No retry mechanism.
33     """
34     def log_args():
35         logger.error('md_input: {!r}'.format(md_input))
36         logger.error('kwargs: {!r}'.format(kwargs))
37 
38     inline = kwargs.get('inline', False) is True
39 
40     if settings.ZDS_APP['zmd']['disable_pings'] is True:
41         kwargs['disable_ping'] = True
42 
43     endpoint = FORMAT_ENDPOINTS[output_format]
44 
45     try:
46         timeout = 10
47         if output_format.startswith('tex'):
48             # latex may be really long to generate but it is also restrained by server configuration
49             timeout = 120
50         response = post('{}{}'.format(settings.ZDS_APP['zmd']['server'], endpoint), json={
51             'opts': kwargs,
52             'md': str(md_input),
53         }, timeout=timeout)
54     except HTTPError:
55         logger.exception('An HTTP error happened, markdown rendering failed')
56         log_args()
57         return '', {}, []
58 
59     if response.status_code == 413:
60         return '', {}, [{'message': str(_('Texte trop volumineux.'))}]
61 
62     if response.status_code != 200:
63         logger.error('The markdown server replied with status {} (expected 200)'.format(response.status_code))
64         log_args()
65         return '', {}, []
66 
67     try:
68         content, metadata, messages = response.json()
69         logger.debug('Result %s, %s, %s', content, metadata, messages)
70         if messages:
71             logger.error('Markdown errors %s', json.dumps(messages))
72         content = content.strip()
73         if inline:
74             content = content.replace('</p>\n', '\n\n').replace('\n<p>', '\n')
75         return mark_safe(content), metadata, messages
76     except:  # noqa
77         logger.exception('Unexpected exception raised')
78         log_args()
79         return '', {}, []
80 
81 
82 def render_markdown(md_input, *, on_error=None, **kwargs):
83     """Render a markdown string.
84 
85     Returns a tuple ``(rendered_content, metadata)``, where
86     ``rendered_content`` is a string and ``metadata`` is a dict.
87 
88     Handles errors gracefully by returning an user-friendly HTML
89     string which explains that the Markdown rendering has failed
90     (without any technical details).
91 
92     """
93     content, metadata, messages = _render_markdown_once(md_input, **kwargs)
94     if messages and on_error:
95         on_error([m['message'] for m in messages])
96     if content is not None:
97         # Success!
98         return content, metadata, messages
99 
100     # Oops, something went wrong
101 
102     attempts = kwargs.get('attempts', 0)
103     inline = kwargs.get('inline', False) is True
104 
105     if attempts < MAX_ATTEMPTS:
106         if not kwargs:
107             kwargs = dict()
108         return render_markdown(md_input, **dict(kwargs, attempts=attempts + 1))
109 
110     logger.error('Max attempt count reached, giving up')
111     logger.error('md_input: {!r}'.format(md_input))
112     logger.error('kwargs: {!r}'.format(kwargs))
113 
114     # FIXME: This cannot work with LaTeX.
115     if inline:
116         return mark_safe('<p>{}</p>'.format(json.dumps(messages))), metadata, []
117     else:
118         return mark_safe('<div class="error ico-after"><p>{}</p></div>'.format(json.dumps(messages))), metadata, []
119 
120 
121 @register.filter(name='epub_markdown', needs_autoescape=False)
122 def epub_markdown(md_input, image_directory):
123     return emarkdown(md_input, output_format='epub', images_download_dir=image_directory.absolute,
124                      local_url_to_local_path=[settings.MEDIA_URL + 'galleries/[0-9]+', image_directory.relative])
125 
126 
127 @register.filter(needs_autoescape=False)
128 @stringfilter
129 def emarkdown(md_input, use_jsfiddle='', **kwargs):
130     """
131     :param str md_input: Markdown string.
132     :return: HTML string.
133     :rtype: str
134     """
135     disable_jsfiddle = (use_jsfiddle != 'js')
136 
137     content, metadata, messages = render_markdown(
138         md_input,
139         on_error=lambda m: logger.error('Markdown errors %s', str(m)),
140         **dict(kwargs, disable_jsfiddle=disable_jsfiddle))
141 
142     return content or ''
143 
144 
145 @register.filter(needs_autoescape=False)
146 @stringfilter
147 def emarkdown_preview(md_input, use_jsfiddle='', **kwargs):
148     """
149     Filter markdown string and render it to html.
150 
151     :param str md_input: Markdown string.
152     :return: HTML string.
153     :rtype: str
154     """
155     disable_jsfiddle = (use_jsfiddle != 'js')
156 
157     content, metadata, messages = render_markdown(
158         md_input,
159         **dict(kwargs, disable_jsfiddle=disable_jsfiddle))
160 
161     if messages:
162         content = _('</div><div class="preview-error"><strong>Erreur du serveur Markdown:</strong>\n{}'
163                     .format('<br>- '.join([m['message'] for m in messages])))
164         content = mark_safe(content)
165 
166     return content
167 
168 
169 @register.filter(needs_autoescape=False)
170 @stringfilter
171 def emarkdown_inline(text):
172     """
173     Parses inline elements only and renders HTML. Mainly for member signatures.
174     Although they are inline elements, pings are disabled.
175 
176     :param str text: Markdown string.
177     :return: HTML string.
178     :rtype: str
179     """
180     rendered = emarkdown(text, inline=True)
181     return rendered
182 
183 
184 def sub_hd(match, count):
185     """Replace header shifted."""
186     subt = match.group(1)
187     lvl = match.group('level')
188     header = match.group('header')
189     end = match.group(4)
190 
191     new_content = subt + '#' * count + lvl + header + end
192 
193     return new_content
194 
195 
196 def shift_heading(text, count):
197     """
198     Shift header in markdown document.
199 
200     :param str text: Text to filter.
201     :param int count:
202     :return: Filtered text.
203     :rtype: str
204     """
205     text_by_code = re.split('(```|~~~)', text)
206     starting_code = None
207     for i, element in enumerate(text_by_code):
208         if element in ['```', '~~~'] and not starting_code:
209             starting_code = element
210         elif element == starting_code:
211             starting_code = None
212         elif starting_code is None:
213             text_by_code[i] = re.sub(r'(^|\n)(?P<level>#{1,4})(?P<header>.*?)#*(\n|$)',
214                                      lambda t: sub_hd(t, count), text_by_code[i])
215 
216     return ''.join(text_by_code)
217 
218 
219 @register.filter('shift_heading_1')
220 def shift_heading_1(text):
221     return shift_heading(text, 1)
222 
223 
224 @register.filter('shift_heading_2')
225 def shift_heading_2(text):
226     return shift_heading(text, 2)
227 
228 
229 @register.filter('shift_heading_3')
230 def shift_heading_3(text):
231     return shift_heading(text, 3)
232
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zds/utils/templatetags/emarkdown.py b/zds/utils/templatetags/emarkdown.py
--- a/zds/utils/templatetags/emarkdown.py
+++ b/zds/utils/templatetags/emarkdown.py
@@ -178,7 +178,7 @@
     :rtype: str
     """
     rendered = emarkdown(text, inline=True)
-    return rendered
+    return mark_safe(rendered.replace('<a href=', '<a rel="nofollow" href='))
 
 
 def sub_hd(match, count):

{"golden_diff": "diff --git a/zds/utils/templatetags/emarkdown.py b/zds/utils/templatetags/emarkdown.py\n--- a/zds/utils/templatetags/emarkdown.py\n+++ b/zds/utils/templatetags/emarkdown.py\n@@ -178,7 +178,7 @@\n     :rtype: str\n     \"\"\"\n     rendered = emarkdown(text, inline=True)\n-    return rendered\n+    return mark_safe(rendered.replace('<a href=', '<a rel=\"nofollow\" href='))\n \n \n def sub_hd(match, count):\n", "issue": "SEO et signature : <a rel=\"nofollow\" />\nDans la signature il faudrait voir si on peut facilement ajouter un attribut `rel=\"nofollow\"` pour pr\u00e9server notre SEO. https://github.com/zestedesavoir/zmarkdown/blob/1dded309a2670689a4a3353f9e38b80624c6df1a/packages/zmarkdown/server/handlers.js#L139\r\n\r\n> limitez les liens en signatures \u00e0 des no follow or lien interne.\r\nc\u2019est pas mal (:evil) de partager un lien, mais si A-312 r\u00e9pond 4 fois dans la m\u00eame page, il renvoie 4 fois du jus sur son compte twitter, 4 coding game, \u2026 ca a plusieurs effet n\u00e9gatifs\r\nSource: https://zestedesavoir.com/forums/sujet/12099/seo-et-spam/?page=1#p199005\r\n\r\n\n", "before_files": [{"content": "import re\nimport json\nimport logging\nfrom requests import post, HTTPError\n\nfrom django import template\nfrom django.conf import settings\nfrom django.template.defaultfilters import stringfilter\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import ugettext_lazy as _\n\nlogger = logging.getLogger(__name__)\nregister = template.Library()\n\"\"\"\nMarkdown related filters.\n\"\"\"\n\n# Constants\nMAX_ATTEMPTS = 3\nMD_PARSING_ERROR = _('Une erreur est survenue dans la g\u00e9n\u00e9ration de texte Markdown. Veuillez rapporter le bug.')\n\nFORMAT_ENDPOINTS = {\n    'html': '/html',\n    'texfile': '/latex-document',\n    'epub': '/epub',\n    'tex': '/latex',\n}\n\n\ndef _render_markdown_once(md_input, *, output_format='html', **kwargs):\n    \"\"\"\n    Returns None on error (error details are logged). No retry mechanism.\n    \"\"\"\n    def log_args():\n        logger.error('md_input: {!r}'.format(md_input))\n        logger.error('kwargs: {!r}'.format(kwargs))\n\n    inline = kwargs.get('inline', False) is True\n\n    if settings.ZDS_APP['zmd']['disable_pings'] is True:\n        kwargs['disable_ping'] = True\n\n    endpoint = FORMAT_ENDPOINTS[output_format]\n\n    try:\n        timeout = 10\n        if output_format.startswith('tex'):\n            # latex may be really long to generate but it is also restrained by server configuration\n            timeout = 120\n        response = post('{}{}'.format(settings.ZDS_APP['zmd']['server'], endpoint), json={\n            'opts': kwargs,\n            'md': str(md_input),\n        }, timeout=timeout)\n    except HTTPError:\n        logger.exception('An HTTP error happened, markdown rendering failed')\n        log_args()\n        return '', {}, []\n\n    if response.status_code == 413:\n        return '', {}, [{'message': str(_('Texte trop volumineux.'))}]\n\n    if response.status_code != 200:\n        logger.error('The markdown server replied with status {} (expected 200)'.format(response.status_code))\n        log_args()\n        return '', {}, []\n\n    try:\n        content, metadata, messages = response.json()\n        logger.debug('Result %s, %s, %s', content, metadata, messages)\n        if messages:\n            logger.error('Markdown errors %s', json.dumps(messages))\n        content = content.strip()\n        if inline:\n            content = content.replace('</p>\\n', '\\n\\n').replace('\\n<p>', '\\n')\n        return mark_safe(content), metadata, messages\n    except:  # noqa\n        logger.exception('Unexpected exception raised')\n        log_args()\n        return '', {}, []\n\n\ndef render_markdown(md_input, *, on_error=None, **kwargs):\n    \"\"\"Render a markdown string.\n\n    Returns a tuple ``(rendered_content, metadata)``, where\n    ``rendered_content`` is a string and ``metadata`` is a dict.\n\n    Handles errors gracefully by returning an user-friendly HTML\n    string which explains that the Markdown rendering has failed\n    (without any technical details).\n\n    \"\"\"\n    content, metadata, messages = _render_markdown_once(md_input, **kwargs)\n    if messages and on_error:\n        on_error([m['message'] for m in messages])\n    if content is not None:\n        # Success!\n        return content, metadata, messages\n\n    # Oops, something went wrong\n\n    attempts = kwargs.get('attempts', 0)\n    inline = kwargs.get('inline', False) is True\n\n    if attempts < MAX_ATTEMPTS:\n        if not kwargs:\n            kwargs = dict()\n        return render_markdown(md_input, **dict(kwargs, attempts=attempts + 1))\n\n    logger.error('Max attempt count reached, giving up')\n    logger.error('md_input: {!r}'.format(md_input))\n    logger.error('kwargs: {!r}'.format(kwargs))\n\n    # FIXME: This cannot work with LaTeX.\n    if inline:\n        return mark_safe('<p>{}</p>'.format(json.dumps(messages))), metadata, []\n    else:\n        return mark_safe('<div class=\"error ico-after\"><p>{}</p></div>'.format(json.dumps(messages))), metadata, []\n\n\n@register.filter(name='epub_markdown', needs_autoescape=False)\ndef epub_markdown(md_input, image_directory):\n    return emarkdown(md_input, output_format='epub', images_download_dir=image_directory.absolute,\n                     local_url_to_local_path=[settings.MEDIA_URL + 'galleries/[0-9]+', image_directory.relative])\n\n\n@register.filter(needs_autoescape=False)\n@stringfilter\ndef emarkdown(md_input, use_jsfiddle='', **kwargs):\n    \"\"\"\n    :param str md_input: Markdown string.\n    :return: HTML string.\n    :rtype: str\n    \"\"\"\n    disable_jsfiddle = (use_jsfiddle != 'js')\n\n    content, metadata, messages = render_markdown(\n        md_input,\n        on_error=lambda m: logger.error('Markdown errors %s', str(m)),\n        **dict(kwargs, disable_jsfiddle=disable_jsfiddle))\n\n    return content or ''\n\n\n@register.filter(needs_autoescape=False)\n@stringfilter\ndef emarkdown_preview(md_input, use_jsfiddle='', **kwargs):\n    \"\"\"\n    Filter markdown string and render it to html.\n\n    :param str md_input: Markdown string.\n    :return: HTML string.\n    :rtype: str\n    \"\"\"\n    disable_jsfiddle = (use_jsfiddle != 'js')\n\n    content, metadata, messages = render_markdown(\n        md_input,\n        **dict(kwargs, disable_jsfiddle=disable_jsfiddle))\n\n    if messages:\n        content = _('</div><div class=\"preview-error\"><strong>Erreur du serveur Markdown:</strong>\\n{}'\n                    .format('<br>- '.join([m['message'] for m in messages])))\n        content = mark_safe(content)\n\n    return content\n\n\n@register.filter(needs_autoescape=False)\n@stringfilter\ndef emarkdown_inline(text):\n    \"\"\"\n    Parses inline elements only and renders HTML. Mainly for member signatures.\n    Although they are inline elements, pings are disabled.\n\n    :param str text: Markdown string.\n    :return: HTML string.\n    :rtype: str\n    \"\"\"\n    rendered = emarkdown(text, inline=True)\n    return rendered\n\n\ndef sub_hd(match, count):\n    \"\"\"Replace header shifted.\"\"\"\n    subt = match.group(1)\n    lvl = match.group('level')\n    header = match.group('header')\n    end = match.group(4)\n\n    new_content = subt + '#' * count + lvl + header + end\n\n    return new_content\n\n\ndef shift_heading(text, count):\n    \"\"\"\n    Shift header in markdown document.\n\n    :param str text: Text to filter.\n    :param int count:\n    :return: Filtered text.\n    :rtype: str\n    \"\"\"\n    text_by_code = re.split('(```|~~~)', text)\n    starting_code = None\n    for i, element in enumerate(text_by_code):\n        if element in ['```', '~~~'] and not starting_code:\n            starting_code = element\n        elif element == starting_code:\n            starting_code = None\n        elif starting_code is None:\n            text_by_code[i] = re.sub(r'(^|\\n)(?P<level>#{1,4})(?P<header>.*?)#*(\\n|$)',\n                                     lambda t: sub_hd(t, count), text_by_code[i])\n\n    return ''.join(text_by_code)\n\n\n@register.filter('shift_heading_1')\ndef shift_heading_1(text):\n    return shift_heading(text, 1)\n\n\n@register.filter('shift_heading_2')\ndef shift_heading_2(text):\n    return shift_heading(text, 2)\n\n\n@register.filter('shift_heading_3')\ndef shift_heading_3(text):\n    return shift_heading(text, 3)\n", "path": "zds/utils/templatetags/emarkdown.py"}], "after_files": [{"content": "import re\nimport json\nimport logging\nfrom requests import post, HTTPError\n\nfrom django import template\nfrom django.conf import settings\nfrom django.template.defaultfilters import stringfilter\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import ugettext_lazy as _\n\nlogger = logging.getLogger(__name__)\nregister = template.Library()\n\"\"\"\nMarkdown related filters.\n\"\"\"\n\n# Constants\nMAX_ATTEMPTS = 3\nMD_PARSING_ERROR = _('Une erreur est survenue dans la g\u00e9n\u00e9ration de texte Markdown. Veuillez rapporter le bug.')\n\nFORMAT_ENDPOINTS = {\n    'html': '/html',\n    'texfile': '/latex-document',\n    'epub': '/epub',\n    'tex': '/latex',\n}\n\n\ndef _render_markdown_once(md_input, *, output_format='html', **kwargs):\n    \"\"\"\n    Returns None on error (error details are logged). No retry mechanism.\n    \"\"\"\n    def log_args():\n        logger.error('md_input: {!r}'.format(md_input))\n        logger.error('kwargs: {!r}'.format(kwargs))\n\n    inline = kwargs.get('inline', False) is True\n\n    if settings.ZDS_APP['zmd']['disable_pings'] is True:\n        kwargs['disable_ping'] = True\n\n    endpoint = FORMAT_ENDPOINTS[output_format]\n\n    try:\n        timeout = 10\n        if output_format.startswith('tex'):\n            # latex may be really long to generate but it is also restrained by server configuration\n            timeout = 120\n        response = post('{}{}'.format(settings.ZDS_APP['zmd']['server'], endpoint), json={\n            'opts': kwargs,\n            'md': str(md_input),\n        }, timeout=timeout)\n    except HTTPError:\n        logger.exception('An HTTP error happened, markdown rendering failed')\n        log_args()\n        return '', {}, []\n\n    if response.status_code == 413:\n        return '', {}, [{'message': str(_('Texte trop volumineux.'))}]\n\n    if response.status_code != 200:\n        logger.error('The markdown server replied with status {} (expected 200)'.format(response.status_code))\n        log_args()\n        return '', {}, []\n\n    try:\n        content, metadata, messages = response.json()\n        logger.debug('Result %s, %s, %s', content, metadata, messages)\n        if messages:\n            logger.error('Markdown errors %s', json.dumps(messages))\n        content = content.strip()\n        if inline:\n            content = content.replace('</p>\\n', '\\n\\n').replace('\\n<p>', '\\n')\n        return mark_safe(content), metadata, messages\n    except:  # noqa\n        logger.exception('Unexpected exception raised')\n        log_args()\n        return '', {}, []\n\n\ndef render_markdown(md_input, *, on_error=None, **kwargs):\n    \"\"\"Render a markdown string.\n\n    Returns a tuple ``(rendered_content, metadata)``, where\n    ``rendered_content`` is a string and ``metadata`` is a dict.\n\n    Handles errors gracefully by returning an user-friendly HTML\n    string which explains that the Markdown rendering has failed\n    (without any technical details).\n\n    \"\"\"\n    content, metadata, messages = _render_markdown_once(md_input, **kwargs)\n    if messages and on_error:\n        on_error([m['message'] for m in messages])\n    if content is not None:\n        # Success!\n        return content, metadata, messages\n\n    # Oops, something went wrong\n\n    attempts = kwargs.get('attempts', 0)\n    inline = kwargs.get('inline', False) is True\n\n    if attempts < MAX_ATTEMPTS:\n        if not kwargs:\n            kwargs = dict()\n        return render_markdown(md_input, **dict(kwargs, attempts=attempts + 1))\n\n    logger.error('Max attempt count reached, giving up')\n    logger.error('md_input: {!r}'.format(md_input))\n    logger.error('kwargs: {!r}'.format(kwargs))\n\n    # FIXME: This cannot work with LaTeX.\n    if inline:\n        return mark_safe('<p>{}</p>'.format(json.dumps(messages))), metadata, []\n    else:\n        return mark_safe('<div class=\"error ico-after\"><p>{}</p></div>'.format(json.dumps(messages))), metadata, []\n\n\n@register.filter(name='epub_markdown', needs_autoescape=False)\ndef epub_markdown(md_input, image_directory):\n    return emarkdown(md_input, output_format='epub', images_download_dir=image_directory.absolute,\n                     local_url_to_local_path=[settings.MEDIA_URL + 'galleries/[0-9]+', image_directory.relative])\n\n\n@register.filter(needs_autoescape=False)\n@stringfilter\ndef emarkdown(md_input, use_jsfiddle='', **kwargs):\n    \"\"\"\n    :param str md_input: Markdown string.\n    :return: HTML string.\n    :rtype: str\n    \"\"\"\n    disable_jsfiddle = (use_jsfiddle != 'js')\n\n    content, metadata, messages = render_markdown(\n        md_input,\n        on_error=lambda m: logger.error('Markdown errors %s', str(m)),\n        **dict(kwargs, disable_jsfiddle=disable_jsfiddle))\n\n    return content or ''\n\n\n@register.filter(needs_autoescape=False)\n@stringfilter\ndef emarkdown_preview(md_input, use_jsfiddle='', **kwargs):\n    \"\"\"\n    Filter markdown string and render it to html.\n\n    :param str md_input: Markdown string.\n    :return: HTML string.\n    :rtype: str\n    \"\"\"\n    disable_jsfiddle = (use_jsfiddle != 'js')\n\n    content, metadata, messages = render_markdown(\n        md_input,\n        **dict(kwargs, disable_jsfiddle=disable_jsfiddle))\n\n    if messages:\n        content = _('</div><div class=\"preview-error\"><strong>Erreur du serveur Markdown:</strong>\\n{}'\n                    .format('<br>- '.join([m['message'] for m in messages])))\n        content = mark_safe(content)\n\n    return content\n\n\n@register.filter(needs_autoescape=False)\n@stringfilter\ndef emarkdown_inline(text):\n    \"\"\"\n    Parses inline elements only and renders HTML. Mainly for member signatures.\n    Although they are inline elements, pings are disabled.\n\n    :param str text: Markdown string.\n    :return: HTML string.\n    :rtype: str\n    \"\"\"\n    rendered = emarkdown(text, inline=True)\n    return mark_safe(rendered.replace('<a href=', '<a rel=\"nofollow\" href='))\n\n\ndef sub_hd(match, count):\n    \"\"\"Replace header shifted.\"\"\"\n    subt = match.group(1)\n    lvl = match.group('level')\n    header = match.group('header')\n    end = match.group(4)\n\n    new_content = subt + '#' * count + lvl + header + end\n\n    return new_content\n\n\ndef shift_heading(text, count):\n    \"\"\"\n    Shift header in markdown document.\n\n    :param str text: Text to filter.\n    :param int count:\n    :return: Filtered text.\n    :rtype: str\n    \"\"\"\n    text_by_code = re.split('(```|~~~)', text)\n    starting_code = None\n    for i, element in enumerate(text_by_code):\n        if element in ['```', '~~~'] and not starting_code:\n            starting_code = element\n        elif element == starting_code:\n            starting_code = None\n        elif starting_code is None:\n            text_by_code[i] = re.sub(r'(^|\\n)(?P<level>#{1,4})(?P<header>.*?)#*(\\n|$)',\n                                     lambda t: sub_hd(t, count), text_by_code[i])\n\n    return ''.join(text_by_code)\n\n\n@register.filter('shift_heading_1')\ndef shift_heading_1(text):\n    return shift_heading(text, 1)\n\n\n@register.filter('shift_heading_2')\ndef shift_heading_2(text):\n    return shift_heading(text, 2)\n\n\n@register.filter('shift_heading_3')\ndef shift_heading_3(text):\n    return shift_heading(text, 3)\n", "path": "zds/utils/templatetags/emarkdown.py"}]}