Ticket #35533: changes.patch

File changes.patch, 11.1 KB (added by DongwookKim0823, 3 months ago)
  • django/utils/html.py

    diff --git a/django/utils/html.py b/django/utils/html.py
    index 22d3ae42fa..a4be3cf8e6 100644
    a b from django.utils.deprecation import RemovedInDjango60Warning  
    1111from django.utils.encoding import punycode
    1212from django.utils.functional import Promise, keep_lazy, keep_lazy_text
    1313from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
     14from django.utils.markdown import find_closing_markdown_bracket, has_markdown_link
    1415from django.utils.regex_helper import _lazy_re_compile
    1516from django.utils.safestring import SafeData, SafeString, mark_safe
    1617from django.utils.text import normalize_newlines
    class Urlizer:  
    278279
    279280    mailto_template = "mailto:{local}@{domain}"
    280281    url_template = '<a href="{href}"{attrs}>{url}</a>'
     282    markdown_url_template = '[{text}](<a href="{url}"{attrs}>{trimmed_url}</a>)'
    281283
    282284    def __call__(self, text, trim_url_limit=None, nofollow=False, autoescape=False):
    283285        """
    class Urlizer:  
    291293        """
    292294        safe_input = isinstance(text, SafeData)
    293295
    294         words = self.word_split_re.split(str(text))
     296        text = str(text)
     297        if has_markdown_link(text):
     298            return self.handle_markdown_link(
     299                text,
     300                safe_input=safe_input,
     301                trim_url_limit=trim_url_limit,
     302                nofollow=nofollow,
     303                autoescape=autoescape,
     304            )
     305
     306        words = self.word_split_re.split(text)
    295307        return "".join(
    296308            [
    297309                self.handle_word(
    class Urlizer:  
    305317            ]
    306318        )
    307319
     320    def handle_markdown_link(
     321        self,
     322        text,
     323        *,
     324        safe_input,
     325        trim_url_limit=None,
     326        nofollow=False,
     327        autoescape=False,
     328    ):
     329        nofollow_attr = ' rel="nofollow"' if nofollow else ""
     330
     331        def find_and_replace_link(text):
     332            i = 0
     333            result = []
     334            while i < len(text):
     335                if text[i] == "\\":
     336                    result.append(text[i : i + 2])
     337                    i += 2
     338                    continue
     339                if text[i] == "[":
     340                    start = i
     341                    close_bracket = find_closing_markdown_bracket(text, i + 1)
     342                    if (
     343                        close_bracket != -1
     344                        and close_bracket + 1 < len(text)
     345                        and text[close_bracket + 1] == "("
     346                    ):
     347                        j = close_bracket + 2
     348                        paren_depth = 1
     349                        while j < len(text):
     350                            if text[j] == "\\":
     351                                j += 2
     352                                continue
     353                            if text[j] == "(":
     354                                paren_depth += 1
     355                            elif text[j] == ")":
     356                                paren_depth -= 1
     357                                if paren_depth == 0:
     358                                    link_text = text[start + 1 : close_bracket]
     359                                    link_url = text[close_bracket + 2 : j]
     360                                    trimmed_url = self.trim_url(
     361                                        link_url, limit=trim_url_limit
     362                                    )
     363
     364                                    if autoescape and not safe_input:
     365                                        link_text = escape(link_text)
     366                                        link_url = escape(link_url)
     367                                        trimmed_url = escape(trimmed_url)
     368
     369                                    result.append(
     370                                        self.markdown_url_template.format(
     371                                            text=link_text,
     372                                            url=link_url,
     373                                            attrs=nofollow_attr,
     374                                            trimmed_url=trimmed_url,
     375                                        )
     376                                    )
     377                                    i = j + 1
     378                                    break
     379                            j += 1
     380                        else:
     381                            result.append(text[i])
     382                            i += 1
     383                    else:
     384                        result.append(text[i])
     385                        i = close_bracket + 1 if close_bracket != -1 else i + 1
     386                else:
     387                    result.append(text[i])
     388                    i += 1
     389            return "".join(result)
     390
     391        return find_and_replace_link(text)
     392
    308393    def handle_word(
    309394        self,
    310395        word,
  • new file django/utils/markdown.py

    diff --git a/django/utils/markdown.py b/django/utils/markdown.py
    new file mode 100644
    index 0000000000..c0213ed47a
    - +  
     1def find_closing_markdown_bracket(text, start):
     2    """
     3    Find the closing bracket corresponding to the opening bracket.
     4    """
     5    depth = 0
     6    i = start
     7    while i < len(text):
     8        if text[i] == "\\":
     9            i += 2
     10            continue
     11        if text[i] == "[":
     12            depth += 1
     13        elif text[i] == "]":
     14            if depth == 0:
     15                return i
     16            depth -= 1
     17        i += 1
     18    return -1
     19
     20
     21def has_markdown_link(text):
     22    """
     23    Check if the given text contains any Markdown links.
     24    """
     25
     26    def is_valid_url(start, end):
     27        """
     28        Check if the URL is valid.
     29        """
     30        url = text[start:end].strip()
     31        return (
     32            url.startswith("http://")
     33            or url.startswith("https://")
     34            or any(c.isalnum() for c in url)
     35        )
     36
     37    i = 0
     38    while i < len(text):
     39        if text[i] == "\\":
     40            i += 2
     41            continue
     42        if text[i] == "[":
     43            close_bracket = find_closing_markdown_bracket(text, i + 1)
     44            if (
     45                close_bracket != -1
     46                and close_bracket + 1 < len(text)
     47                and text[close_bracket + 1] == "("
     48            ):
     49                j = close_bracket + 2
     50                paren_depth = 1
     51                while j < len(text):
     52                    if text[j] == "\\":
     53                        j += 2
     54                        continue
     55                    if text[j] == "(":
     56                        paren_depth += 1
     57                    elif text[j] == ")":
     58                        paren_depth -= 1
     59                        if paren_depth == 0:
     60                            if is_valid_url(close_bracket + 2, j):
     61                                return True
     62                            break
     63                    j += 1
     64            i = close_bracket + 1 if close_bracket != -1 else i + 1
     65        else:
     66            i += 1
     67    return False
  • tests/template_tests/filter_tests/test_urlize.py

    diff --git a/tests/template_tests/filter_tests/test_urlize.py b/tests/template_tests/filter_tests/test_urlize.py
    index 8f84e62c92..1b377883f9 100644
    a b class FunctionTests(SimpleTestCase):  
    320320        )
    321321        self.assertEqual(
    322322            urlize("[http://168.192.0.1](http://168.192.0.1)"),
    323             '[<a href="http://168.192.0.1](http://168.192.0.1)" rel="nofollow">'
    324             "http://168.192.0.1](http://168.192.0.1)</a>",
     323            '[http://168.192.0.1](<a href="http://168.192.0.1" rel="nofollow">'
     324            "http://168.192.0.1</a>)",
    325325        )
    326326
    327327    def test_wrapping_characters(self):
  • tests/utils_tests/test_html.py

    diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
    index ad31b8cc5b..ecb9bc2ea2 100644
    a b from django.utils.html import (  
    1717    strip_spaces_between_tags,
    1818    strip_tags,
    1919    urlize,
     20    urlizer,
    2021)
    2122from django.utils.safestring import mark_safe
    2223
    class TestUtilsHtml(SimpleTestCase):  
    356357        for value in tests:
    357358            with self.subTest(value=value):
    358359                self.assertEqual(urlize(value), value)
     360
     361    def test_handle_markdown_link(self):
     362        tests = [
     363            {
     364                "input": "Here's a [link with [nested] brackets](https://example.com)",
     365                "expected": "Here's a [link with [nested] brackets](<a href=\"https://"
     366                'example.com">https://example.com</a>)',
     367                "params": {
     368                    "trim_url_limit": None,
     369                    "nofollow": False,
     370                    "autoescape": False,
     371                },
     372            },
     373            {
     374                "input": "Check out [this link](https://example.com/page(1))",
     375                "expected": 'Check out [this link](<a href="https://example.com/'
     376                'page(1)">https://example.com/page(1)</a>)',
     377                "params": {
     378                    "trim_url_limit": None,
     379                    "nofollow": False,
     380                    "autoescape": False,
     381                },
     382            },
     383            {
     384                "input": "Here's a [complex URL](https://example.com/"
     385                "path?param1=value1&param2=value2#fragment)",
     386                "expected": "Here's a [complex URL](<a href=\"https://example.com/"
     387                'path?param1=value1&amp;param2=value2#fragment">'
     388                "https://example.com/path?param1=value1&amp;"
     389                "param2=value2#fragment</a>)",
     390                "params": {
     391                    "trim_url_limit": None,
     392                    "nofollow": False,
     393                    "autoescape": True,
     394                },
     395            },
     396            {
     397                "input": "Multiple [link1](https://example1.com) and "
     398                "[link2](https://example2.com)",
     399                "expected": 'Multiple [link1](<a href="https://example1.com">'
     400                "https://example1.com</a>) and [link2]"
     401                '(<a href="https://example2.com">https://example2.com</a>)',
     402                "params": {
     403                    "trim_url_limit": None,
     404                    "nofollow": False,
     405                    "autoescape": False,
     406                },
     407            },
     408            {
     409                "input": "This is a [broken link(https://example.com)",
     410                "expected": "This is a [broken link(https://example.com)",
     411                "params": {
     412                    "trim_url_limit": None,
     413                    "nofollow": False,
     414                    "autoescape": False,
     415                },
     416            },
     417            {
     418                "input": "Here's a [very long URL](https://example.com/"
     419                + "x" * 100
     420                + ")",
     421                "expected": "Here's a [very long URL](<a href=\"https://example.com/"
     422                + "x" * 100
     423                + '">https://example.com/xxxxxxxxx…</a>)',
     424                "params": {
     425                    "trim_url_limit": 30,
     426                    "nofollow": False,
     427                    "autoescape": False,
     428                },
     429            },
     430        ]
     431        for test in tests:
     432            with self.subTest(test=test):
     433                output = urlizer(test["input"], **test["params"])
     434                self.assertEqual(output, test["expected"])
Back to Top