Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-markdown2 for 
openSUSE:Factory checked in at 2025-09-22 16:39:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-markdown2 (Old)
 and      /work/SRC/openSUSE:Factory/.python-markdown2.new.27445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-markdown2"

Mon Sep 22 16:39:44 2025 rev:18 rq:1306359 version:2.5.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-markdown2/python-markdown2.changes        
2025-02-18 19:13:57.576678644 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-markdown2.new.27445/python-markdown2.changes 
    2025-09-22 16:40:33.711635166 +0200
@@ -1,0 +2,13 @@
+Sun Sep 21 19:34:41 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 2.5.4:
+  * [pull #617] Add MarkdownFileLinks extra
+  * [pull #622] Add missing block tags to regex
+  * [pull #623] Don't escape plus signs in URLs
+  * [pull #626] Fix XSS when encoding incomplete tags
+  * [pull #628] Fix TypeError in MiddleWordEm extra when options
+    was None
+  * [pull #630] Fix nbsp breaking tables
+  * [pull #634] Fix ReDoS in HTML tokenizer regex
+
+-------------------------------------------------------------------

Old:
----
  markdown2-2.5.3.tar.gz

New:
----
  markdown2-2.5.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-markdown2.spec ++++++
--- /var/tmp/diff_new_pack.BplFV3/_old  2025-09-22 16:40:34.399664074 +0200
+++ /var/tmp/diff_new_pack.BplFV3/_new  2025-09-22 16:40:34.399664074 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-markdown2
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-markdown2
-Version:        2.5.3
+Version:        2.5.4
 Release:        0
 Summary:        A Python implementation of Markdown
 License:        MIT

++++++ markdown2-2.5.3.tar.gz -> markdown2-2.5.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/CHANGES.md 
new/markdown2-2.5.4/CHANGES.md
--- old/markdown2-2.5.3/CHANGES.md      2025-01-24 22:13:35.000000000 +0100
+++ new/markdown2-2.5.4/CHANGES.md      2025-07-27 18:16:14.000000000 +0200
@@ -1,5 +1,16 @@
 # python-markdown2 Changelog
 
+## python-markdown2 2.5.4
+
+- [pull #617] Add MarkdownFileLinks extra (#528)
+- [pull #622] Add missing block tags to regex (#620)
+- [pull #623] Don't escape plus signs in URLs (#621)
+- [pull #626] Fix XSS when encoding incomplete tags (#625)
+- [pull #628] Fix TypeError in MiddleWordEm extra when options was None (#627)
+- [pull #630] Fix nbsp breaking tables (#629)
+- [pull #634] Fix ReDoS in HTML tokenizer regex (#633)
+
+
 ## python-markdown2 2.5.3
 
 - [pull #616] make tables without body gfm compatible
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/Makefile new/markdown2-2.5.4/Makefile
--- old/markdown2-2.5.3/Makefile        2023-02-12 23:32:18.000000000 +0100
+++ new/markdown2-2.5.4/Makefile        2025-07-27 18:14:04.000000000 +0200
@@ -12,6 +12,10 @@
 testone:
        cd test && python test.py -- -knownfailure
 
+.PHONY: testredos
+testredos:
+       python test/test_redos.py
+
 .PHONY: pygments
 pygments:
        [[ -d deps/pygments ]] || ( \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/PKG-INFO new/markdown2-2.5.4/PKG-INFO
--- old/markdown2-2.5.3/PKG-INFO        2025-01-24 22:13:43.557940000 +0100
+++ new/markdown2-2.5.4/PKG-INFO        2025-07-27 18:16:21.453108500 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: markdown2
-Version: 2.5.3
+Version: 2.5.4
 Summary: A fast and complete Python implementation of Markdown
 Home-page: https://github.com/trentm/python-markdown2
 Author: Trent Mick
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/lib/markdown2.egg-info/PKG-INFO 
new/markdown2-2.5.4/lib/markdown2.egg-info/PKG-INFO
--- old/markdown2-2.5.3/lib/markdown2.egg-info/PKG-INFO 2025-01-24 
22:13:43.000000000 +0100
+++ new/markdown2-2.5.4/lib/markdown2.egg-info/PKG-INFO 2025-07-27 
18:16:21.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: markdown2
-Version: 2.5.3
+Version: 2.5.4
 Summary: A fast and complete Python implementation of Markdown
 Home-page: https://github.com/trentm/python-markdown2
 Author: Trent Mick
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/lib/markdown2.egg-info/SOURCES.txt 
new/markdown2-2.5.4/lib/markdown2.egg-info/SOURCES.txt
--- old/markdown2-2.5.3/lib/markdown2.egg-info/SOURCES.txt      2025-01-24 
22:13:43.000000000 +0100
+++ new/markdown2-2.5.4/lib/markdown2.egg-info/SOURCES.txt      2025-07-27 
18:16:21.000000000 +0200
@@ -16,6 +16,7 @@
 test/api.doctests
 test/test.py
 test/test_markdown2.py
+test/test_redos.py
 test/testall.py
 test/testlib.py
 test/tm-cases/CVE-2018-5773.html
@@ -141,6 +142,9 @@
 test/tm-cases/empty_fenced_code_blocks.opts
 test/tm-cases/empty_fenced_code_blocks.tags
 test/tm-cases/empty_fenced_code_blocks.text
+test/tm-cases/encode_incomplete_tags_xss_issue625.html
+test/tm-cases/encode_incomplete_tags_xss_issue625.opts
+test/tm-cases/encode_incomplete_tags_xss_issue625.text
 test/tm-cases/escape_html_comments_safe_mode.html
 test/tm-cases/escape_html_comments_safe_mode.opts
 test/tm-cases/escape_html_comments_safe_mode.text
@@ -419,6 +423,12 @@
 test/tm-cases/long_link.text
 test/tm-cases/malformed_html_crash_issue584.html
 test/tm-cases/malformed_html_crash_issue584.text
+test/tm-cases/markdown_file_links.html
+test/tm-cases/markdown_file_links.opts
+test/tm-cases/markdown_file_links.text
+test/tm-cases/markdown_file_links_no_linkdefs.html
+test/tm-cases/markdown_file_links_no_linkdefs.opts
+test/tm-cases/markdown_file_links_no_linkdefs.text
 test/tm-cases/markdown_in_html.html
 test/tm-cases/markdown_in_html.opts
 test/tm-cases/markdown_in_html.tags
@@ -447,6 +457,9 @@
 test/tm-cases/middle_word_em.html
 test/tm-cases/middle_word_em.opts
 test/tm-cases/middle_word_em.text
+test/tm-cases/middle_word_em_issue627.html
+test/tm-cases/middle_word_em_issue627.opts
+test/tm-cases/middle_word_em_issue627.text
 test/tm-cases/middle_word_em_with_extra_ems.html
 test/tm-cases/middle_word_em_with_extra_ems.opts
 test/tm-cases/middle_word_em_with_extra_ems.text
@@ -507,6 +520,9 @@
 test/tm-cases/relative_links_safe_mode.html
 test/tm-cases/relative_links_safe_mode.opts
 test/tm-cases/relative_links_safe_mode.text
+test/tm-cases/safe_mode_issue621.html
+test/tm-cases/safe_mode_issue621.opts
+test/tm-cases/safe_mode_issue621.text
 test/tm-cases/script-and-style-blocks.html
 test/tm-cases/script-and-style-blocks.text
 test/tm-cases/seperated_list_items.html
@@ -522,6 +538,9 @@
 test/tm-cases/smarty_pants_issue611.html
 test/tm-cases/smarty_pants_issue611.opts
 test/tm-cases/smarty_pants_issue611.text
+test/tm-cases/smarty_pants_issue620.html
+test/tm-cases/smarty_pants_issue620.opts
+test/tm-cases/smarty_pants_issue620.text
 test/tm-cases/spoiler.html
 test/tm-cases/spoiler.opts
 test/tm-cases/spoiler.text
@@ -539,6 +558,9 @@
 test/tm-cases/tables.opts
 test/tm-cases/tables.tags
 test/tm-cases/tables.text
+test/tm-cases/tables_nbsp_issue629.html
+test/tm-cases/tables_nbsp_issue629.opts
+test/tm-cases/tables_nbsp_issue629.text
 test/tm-cases/task_list.html
 test/tm-cases/task_list.opts
 test/tm-cases/task_list.tags
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/lib/markdown2.py 
new/markdown2-2.5.4/lib/markdown2.py
--- old/markdown2-2.5.3/lib/markdown2.py        2024-12-27 19:04:02.000000000 
+0100
+++ new/markdown2-2.5.4/lib/markdown2.py        2025-07-27 18:14:04.000000000 
+0200
@@ -66,6 +66,7 @@
   references, revision number references).
 * link-shortrefs: allow shortcut reference links, not followed by `[]` or
   a link label.
+* markdown-file-links: Replace links to `.md` files with `.html` links
 * markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to
   have markdown processing be done on its contents. Similar to
   <http://michelf.com/projects/php-markdown/extra/#markdown-attr> but with
@@ -108,7 +109,7 @@
 #   not yet sure if there implications with this. Compare 'pydoc sre'
 #   and 'perldoc perlre'.
 
-__version_info__ = (2, 5, 3)
+__version_info__ = (2, 5, 4)
 __version__ = '.'.join(map(str, __version_info__))
 __author__ = "Trent Mick"
 
@@ -858,9 +859,9 @@
 
     # I broke out the html5 tags here and add them to _block_tags_a and
     # _block_tags_b.  This way html5 tags are easy to keep track of.
-    _html5tags = 
'|article|aside|header|hgroup|footer|nav|section|figure|figcaption'
+    _html5tags = 
'|address|article|aside|canvas|figcaption|figure|footer|header|main|nav|section|video'
 
-    _block_tags_a = 
'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|html|head|body'
+    _block_tags_a = 
'blockquote|body|dd|del|div|dl|dt|fieldset|form|h[1-6]|head|hr|html|iframe|ins|li|math|noscript|ol|p|pre|script|style|table|tfoot|ul'
     _block_tags_a += _html5tags
 
     _strict_tag_block_re = re.compile(r"""
@@ -876,7 +877,7 @@
         """ % _block_tags_a,
         re.X | re.M)
 
-    _block_tags_b = 
'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math'
+    _block_tags_b = 
'blockquote|div|dl|fieldset|form|h[1-6]|iframe|math|noscript|ol|p|pre|script|table|ul'
     _block_tags_b += _html5tags
 
     _span_tags = (
@@ -1272,7 +1273,7 @@
                     \s+                           # whitespace after tag
                     (?:[^\t<>"'=/]+:)?
                     [^<>"'=/]+=                   # attr name
-                    (?:".*?"|'.*?'|[^<>"'=/\s]+)  # value, quoted or unquoted. 
If unquoted, no spaces allowed
+                    (?:"[^"]*?"|'[^']*?'|[^<>"'=/\s]+)  # value, quoted or 
unquoted. If unquoted, no spaces allowed
                 )*
                 \s*/?>
                 |
@@ -1318,17 +1319,17 @@
             is_html_markup = not is_html_markup
         return ''.join(escaped)
 
+    def _is_auto_link(self, text):
+        if ':' in text and self._auto_link_re.match(text):
+            return True
+        elif '@' in text and self._auto_email_link_re.match(text):
+            return True
+        return False
+
     @mark_stage(Stage.HASH_HTML)
     def _hash_html_spans(self, text: str) -> str:
         # Used for safe_mode.
 
-        def _is_auto_link(s):
-            if ':' in s and self._auto_link_re.match(s):
-                return True
-            elif '@' in s and self._auto_email_link_re.match(s):
-                return True
-            return False
-
         def _is_code_span(index, token):
             try:
                 if token == '<code>':
@@ -1352,7 +1353,7 @@
         split_tokens = self._sorta_html_tokenize_re.split(text)
         is_html_markup = False
         for index, token in enumerate(split_tokens):
-            if is_html_markup and not _is_auto_link(token) and not 
_is_code_span(index, token):
+            if is_html_markup and not self._is_auto_link(token) and not 
_is_code_span(index, token):
                 is_comment = _is_comment(token)
                 if is_comment:
                     
tokens.append(self._hash_span(self._sanitize_html(is_comment.group(1))))
@@ -1446,25 +1447,6 @@
             i += 1
         return i
 
-    def _extract_url_and_title(self, text: str, start: int) -> 
Union[tuple[str, str, int], tuple[None, None, None]]:
-        """Extracts the url and (optional) title from the tail of a link"""
-        # text[start] equals the opening parenthesis
-        idx = self._find_non_whitespace(text, start+1)
-        if idx == len(text):
-            return None, None, None
-        end_idx = idx
-        has_anglebrackets = text[idx] == "<"
-        if has_anglebrackets:
-            end_idx = self._find_balanced(text, end_idx+1, "<", ">")
-        end_idx = self._find_balanced(text, end_idx, "(", ")")
-        match = self._inline_link_title.search(text, idx, end_idx)
-        if not match:
-            return None, None, None
-        url, title = text[idx:match.start()], match.group("title")
-        if has_anglebrackets:
-            url = self._strip_anglebrackets.sub(r'\1', url)
-        return url, title, end_idx
-
     # 
https://developer.mozilla.org/en-US/docs/web/http/basics_of_http/data_urls
     # 
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
     _data_url_re = re.compile(r'''
@@ -1523,180 +1505,9 @@
         Markdown.pl because of the lack of atomic matching support in
         Python's regex engine used in $g_nested_brackets.
         """
-        MAX_LINK_TEXT_SENTINEL = 3000  # markdown2 issue 24
-
-        # `anchor_allowed_pos` is used to support img links inside
-        # anchors, but not anchors inside anchors. An anchor's start
-        # pos must be `>= anchor_allowed_pos`.
-        anchor_allowed_pos = 0
-
-        curr_pos = 0
-
-        while True:
-            # The next '[' is the start of:
-            # - an inline anchor:   [text](url "title")
-            # - a reference anchor: [text][id]
-            # - an inline img:      ![text](url "title")
-            # - a reference img:    ![text][id]
-            # - a footnote ref:     [^id]
-            #   (Only if 'footnotes' extra enabled)
-            # - a footnote defn:    [^id]: ...
-            #   (Only if 'footnotes' extra enabled) These have already
-            #   been stripped in _strip_footnote_definitions() so no
-            #   need to watch for them.
-            # - a link definition:  [id]: url "title"
-            #   These have already been stripped in
-            #   _strip_link_definitions() so no need to watch for them.
-            # - not markup:         [...anything else...
-            try:
-                start_idx = text.index('[', curr_pos)
-            except ValueError:
-                break
-            text_length = len(text)
-
-            # Find the matching closing ']'.
-            # Markdown.pl allows *matching* brackets in link text so we
-            # will here too. Markdown.pl *doesn't* currently allow
-            # matching brackets in img alt text -- we'll differ in that
-            # regard.
-            bracket_depth = 0
-
-            for p in range(
-                start_idx + 1,
-                min(start_idx + MAX_LINK_TEXT_SENTINEL, text_length)
-            ):
-                ch = text[p]
-                if ch == ']':
-                    bracket_depth -= 1
-                    if bracket_depth < 0:
-                        break
-                elif ch == '[':
-                    bracket_depth += 1
-            else:
-                # Closing bracket not found within sentinel length.
-                # This isn't markup.
-                curr_pos = start_idx + 1
-                continue
-            link_text = text[start_idx + 1: p]
-
-            # Fix for issue 341 - Injecting XSS into link text
-            if self.safe_mode:
-                link_text = self._hash_html_spans(link_text)
-                link_text = self._unhash_html_spans(link_text)
-
-            # Possibly a footnote ref?
-            if "footnotes" in self.extras and link_text.startswith("^"):
-                normed_id = re.sub(r'\W', '-', link_text[1:])
-                if normed_id in self.footnotes:
-                    result = (
-                        f'<sup class="footnote-ref" id="fnref-{normed_id}">'
-                        # insert special footnote marker that's easy to find 
and match against later
-                        f'<a 
href="#fn-{normed_id}">{self._footnote_marker}-{normed_id}</a></sup>'
-                    )
-                    text = text[:start_idx] + result + text[p+1:]
-                else:
-                    # This id isn't defined, leave the markup alone.
-                    curr_pos = p + 1
-                continue
-
-            # Now determine what this is by the remainder.
-            p += 1
-
-            # -- Extract the URL, title and end index from the link
-
-            # inline anchor or inline img
-            if text[p:p + 1] == '(':
-                url, title, url_end_idx = self._extract_url_and_title(text, p)
-                if url is None:
-                    # text isn't markup
-                    curr_pos = start_idx + 1
-                    continue
-                url = self._unhash_html_spans(url, code=True)
-            # reference anchor or reference img
-            else:
-                match = None
-                if 'link-shortrefs' in self.extras:
-                    # check if there's no tailing id section
-                    if link_text and re.match(r'[ ]?(?:\n[ ]*)?(?!\[)', 
text[p:]):
-                        # try a match with `[]` inserted into the text
-                        match = 
self._tail_of_reference_link_re.match(f'{text[:p]}[]{text[p:]}', p)
-                        if match:
-                            # if we get a match, we'll have to modify the 
`text` variable to insert the `[]`
-                            # but we ONLY want to do that if the link_id is 
valid. This makes sure that we
-                            # don't get stuck in any loops and also that when 
a user inputs `[abc]` we don't
-                            # output `[abc][]` in the final HTML
-                            if (match.group("id").lower() or 
link_text.lower()) in self.urls:
-                                text = f'{text[:p]}[]{text[p:]}'
-                            else:
-                                match = None
-
-                match = match or self._tail_of_reference_link_re.match(text, p)
-                if not match:
-                    # text isn't markup
-                    curr_pos = start_idx + 1
-                    continue
-
-                link_id = match.group("id").lower() or link_text.lower()  # 
for links like [this][]
-
-                if link_id not in self.urls:
-                    # This id isn't defined, leave the markup alone.
-                    # set current pos to end of link title and continue from 
there
-                    curr_pos = p
-                    continue
-
-                url = self.urls[link_id]
-                title = self.titles.get(link_id)
-                url_end_idx = match.end()
-
-            # -- Encode and hash the URL and title to avoid conflicts with 
italics/bold
-
-            url = (
-                url
-                .replace('*', self._escape_table['*'])
-                .replace('_', self._escape_table['_'])
-            )
-            if title:
-                title = (
-                    _xml_escape_attr(title)
-                    .replace('*', self._escape_table['*'])
-                    .replace('_', self._escape_table['_'])
-                )
-                title_str = f' title="{title}"'
-            else:
-                title_str = ''
-
-            # -- Process the anchor/image
-
-            is_img = start_idx > 0 and text[start_idx-1] == "!"
-            if is_img:
-                start_idx -= 1
-                img_class_str = self._html_class_str_from_tag("img")
-                result = result_head = (
-                    f'<img src="{self._protect_url(url)}"'
-                    f' alt="{self._hash_span(_xml_escape_attr(link_text))}"'
-                    f'{title_str}{img_class_str}{self.empty_element_suffix}'
-                )
-            elif start_idx >= anchor_allowed_pos:
-                if self.safe_mode and not self._safe_href.match(url):
-                    result_head = f'<a href="#"{title_str}>'
-                else:
-                    result_head = f'<a 
href="{self._protect_url(url)}"{title_str}>'
-                result = f'{result_head}{link_text}</a>'
-            else:
-                # anchor not allowed here/invalid markup
-                curr_pos = start_idx + 1
-                continue
-
-            if "smarty-pants" in self.extras:
-                result = result.replace('"', self._escape_table['"'])
-
-            # <img> allowed from curr_pos onwards, <a> allowed from 
anchor_allowed_pos onwards.
-            # this means images can exist within `<a>` tags but anchors can 
only come after the
-            # current anchor has been closed
-            curr_pos = start_idx + len(result_head)
-            anchor_allowed_pos = start_idx + len(result)
-            text = text[:start_idx] + result + text[url_end_idx:]
-
+        link_processor = LinkProcessor(self, None)
+        if link_processor.test(text):
+            text = link_processor.run(text)
         return text
 
     def header_id_from_text(self,
@@ -2354,7 +2165,7 @@
         if self.safe_mode not in ("replace", "escape"):
             return text
 
-        if text.endswith(">"):
+        if self._is_auto_link(text):
             return text  # this is not an incomplete tag, this is a link in 
the form <http://x.y.z>
 
         def incomplete_tags_sub(match):
@@ -2649,7 +2460,7 @@
     strong_re = Markdown._strong_re
     em_re = Markdown._em_re
 
-    def __init__(self, md: Markdown, options: dict):
+    def __init__(self, md: Markdown, options: Optional[dict]):
         super().__init__(md, options)
         self.hash_table = {}
 
@@ -2682,6 +2493,342 @@
             return '*' in text or '_' in text
         return self.hash_table and re.search(r'md5-[0-9a-z]{32}', text)
 
+
+class _LinkProcessorExtraOpts(TypedDict, total=False):
+    '''Options for the `LinkProcessor` extra'''
+    tags: List[str]
+    '''List of tags to be processed by the extra. Default is `['a', 'img']`'''
+    inline: bool
+    '''Whether to process inline links. Default: True'''
+    ref: bool
+    '''Whether to process reference links. Default: True'''
+
+
+class LinkProcessor(Extra):
+    name = 'link-processor'
+    order = (Stage.ITALIC_AND_BOLD,), (Stage.ESCAPE_SPECIAL,)
+    options: _LinkProcessorExtraOpts
+
+    def __init__(self, md: Markdown, options: Optional[dict]):
+        options = options or {}
+        super().__init__(md, options)
+
+    def parse_inline_anchor_or_image(self, text: str, _link_text: str, 
start_idx: int) -> Optional[Tuple[str, str, Optional[str], int]]:
+        '''
+        Parse a string and extract a link from it. This can be an inline 
anchor or an image.
+
+        Args:
+            text: the whole text containing the link
+            link_text: the human readable text inside the link
+            start_idx: the index of the link within `text`
+
+        Returns:
+            None if a link was not able to be parsed from `text`.
+            If successful, a tuple is returned containing:
+
+            1. potentially modified version of the `text` param
+            2. the URL
+            3. the title (can be None if not present)
+            4. the index where the link ends within text
+        '''
+        idx = self.md._find_non_whitespace(text, start_idx + 1)
+        if idx == len(text):
+            return
+        end_idx = idx
+        has_anglebrackets = text[idx] == "<"
+        if has_anglebrackets:
+            end_idx = self.md._find_balanced(text, end_idx+1, "<", ">")
+        end_idx = self.md._find_balanced(text, end_idx, "(", ")")
+        match = self.md._inline_link_title.search(text, idx, end_idx)
+        if not match:
+            return
+        url, title = text[idx:match.start()], match.group("title")
+        if has_anglebrackets:
+            url = self.md._strip_anglebrackets.sub(r'\1', url)
+        return text, url, title, end_idx
+
+    def process_link_shortrefs(self, text: str, link_text: str, start_idx: 
int) -> Tuple[Optional[re.Match], str]:
+        '''
+        Detects shortref links within a string and converts them to normal 
references
+
+        Args:
+            text: the whole text containing the link
+            link_text: the human readable text inside the link
+            start_idx: the index of the link within `text`
+
+        Returns:
+            A tuple containing:
+
+            1. A potential `re.Match` against the link reference within `text` 
(will be None if not found)
+            2. potentially modified version of the `text` param
+        '''
+        match = None
+        # check if there's no tailing id section
+        if link_text and re.match(r'[ ]?(?:\n[ ]*)?(?!\[)', text[start_idx:]):
+            # try a match with `[]` inserted into the text
+            match = 
self.md._tail_of_reference_link_re.match(f'{text[:start_idx]}[]{text[start_idx:]}',
 start_idx)
+            if match:
+                # if we get a match, we'll have to modify the `text` variable 
to insert the `[]`
+                # but we ONLY want to do that if the link_id is valid. This 
makes sure that we
+                # don't get stuck in any loops and also that when a user 
inputs `[abc]` we don't
+                # output `[abc][]` in the final HTML
+                if (match.group("id").lower() or link_text.lower()) in 
self.md.urls:
+                    text = f'{text[:start_idx]}[]{text[start_idx:]}'
+                else:
+                    match = None
+
+        return match, text
+
+    def parse_ref_anchor_or_ref_image(self, text: str, link_text: str, 
start_idx: int) -> Optional[Tuple[str, Optional[str], Optional[str], int]]:
+        '''
+        Parse a string and extract a link from it. This can be a reference 
anchor or image.
+
+        Args:
+            text: the whole text containing the link
+            link_text: the human readable text inside the link
+            start_idx: the index of the link within `text`
+
+        Returns:
+            None if a link was not able to be parsed from `text`.
+            If successful, a tuple is returned containing:
+
+            1. potentially modified version of the `text` param
+            2. the URL (can be None if the reference doesn't exist)
+            3. the title (can be None if not present)
+            4. the index where the link ends within text
+        '''
+        match = None
+        if 'link-shortrefs' in self.md.extras:
+            match, text = self.process_link_shortrefs(text, link_text, 
start_idx)
+
+        match = match or self.md._tail_of_reference_link_re.match(text, 
start_idx)
+        if not match:
+            # text isn't markup
+            return
+
+        link_id = match.group("id").lower() or link_text.lower()  # for links 
like [this][]
+
+        url = self.md.urls.get(link_id)
+        title = self.md.titles.get(link_id)
+        url_end_idx = match.end()
+
+        return text, url, title, url_end_idx
+
+    def process_image(self, url: str, title_attr: str, link_text: str) -> 
Tuple[str, int]:
+        '''
+        Takes a URL, title and link text and returns an HTML `<img>` tag
+
+        Args:
+            url: the image URL/src
+            title_attr: a string containing the title attribute of the tag 
(eg: `' title="..."'`)
+            link_text: the human readable text portion of the link
+
+        Returns:
+            A tuple containing:
+
+            1. The HTML string
+            2. The length of the opening HTML tag in the string. For `<img>` 
it's the whole string.
+               This section will be skipped by the link processor
+        '''
+        img_class_str = self.md._html_class_str_from_tag("img")
+        result = (
+            f'<img src="{self.md._protect_url(url)}"'
+            f' alt="{self.md._hash_span(_xml_escape_attr(link_text))}"'
+            f'{title_attr}{img_class_str}{self.md.empty_element_suffix}'
+        )
+        return result, len(result)
+
+    def process_anchor(self, url: str, title_attr: str, link_text: str) -> 
Tuple[str, int]:
+        '''
+        Takes a URL, title and link text and returns an HTML `<a>` tag
+
+        Args:
+            url: the URL
+            title_attr: a string containing the title attribute of the tag 
(eg: `' title="..."'`)
+            link_text: the human readable text portion of the link
+
+        Returns:
+            A tuple containing:
+
+            1. The HTML string
+            2. The length of the opening HTML tag in the string. This section 
will be skipped
+               by the link processor
+        '''
+        if self.md.safe_mode and not self.md._safe_href.match(url):
+            result_head = f'<a href="#"{title_attr}>'
+        else:
+            result_head = f'<a href="{self.md._protect_url(url)}"{title_attr}>'
+
+        return f'{result_head}{link_text}</a>', len(result_head)
+
+    def run(self, text: str):
+        MAX_LINK_TEXT_SENTINEL = 3000  # markdown2 issue 24
+
+        # `anchor_allowed_pos` is used to support img links inside
+        # anchors, but not anchors inside anchors. An anchor's start
+        # pos must be `>= anchor_allowed_pos`.
+        anchor_allowed_pos = 0
+
+        curr_pos = 0
+
+        while True:
+            # The next '[' is the start of:
+            # - an inline anchor:   [text](url "title")
+            # - a reference anchor: [text][id]
+            # - an inline img:      ![text](url "title")
+            # - a reference img:    ![text][id]
+            # - a footnote ref:     [^id]
+            #   (Only if 'footnotes' extra enabled)
+            # - a footnote defn:    [^id]: ...
+            #   (Only if 'footnotes' extra enabled) These have already
+            #   been stripped in _strip_footnote_definitions() so no
+            #   need to watch for them.
+            # - a link definition:  [id]: url "title"
+            #   These have already been stripped in
+            #   _strip_link_definitions() so no need to watch for them.
+            # - not markup:         [...anything else...
+            try:
+                start_idx = text.index('[', curr_pos)
+            except ValueError:
+                break
+            text_length = len(text)
+
+            # Find the matching closing ']'.
+            # Markdown.pl allows *matching* brackets in link text so we
+            # will here too. Markdown.pl *doesn't* currently allow
+            # matching brackets in img alt text -- we'll differ in that
+            # regard.
+            bracket_depth = 0
+
+            for p in range(
+                start_idx + 1,
+                min(start_idx + MAX_LINK_TEXT_SENTINEL, text_length)
+            ):
+                ch = text[p]
+                if ch == ']':
+                    bracket_depth -= 1
+                    if bracket_depth < 0:
+                        break
+                elif ch == '[':
+                    bracket_depth += 1
+            else:
+                # Closing bracket not found within sentinel length.
+                # This isn't markup.
+                curr_pos = start_idx + 1
+                continue
+            link_text = text[start_idx + 1: p]
+
+            # Fix for issue 341 - Injecting XSS into link text
+            if self.md.safe_mode:
+                link_text = self.md._hash_html_spans(link_text)
+                link_text = self.md._unhash_html_spans(link_text)
+
+            # Possibly a footnote ref?
+            if "footnotes" in self.md.extras and link_text.startswith("^"):
+                normed_id = re.sub(r'\W', '-', link_text[1:])
+                if normed_id in self.md.footnotes:
+                    result = (
+                        f'<sup class="footnote-ref" id="fnref-{normed_id}">'
+                        # insert special footnote marker that's easy to find 
and match against later
+                        f'<a 
href="#fn-{normed_id}">{self.md._footnote_marker}-{normed_id}</a></sup>'
+                    )
+                    text = text[:start_idx] + result + text[p+1:]
+                else:
+                    # This id isn't defined, leave the markup alone.
+                    curr_pos = p + 1
+                continue
+
+            # Now determine what this is by the remainder.
+            p += 1
+
+            # -- Extract the URL, title and end index from the link
+
+            # inline anchor or inline img
+            if text[p:p + 1] == '(':
+                if not self.options.get('inline', True):
+                    curr_pos = start_idx + 1
+                    continue
+
+                parsed = self.parse_inline_anchor_or_image(text, link_text, p)
+                if not parsed:
+                    # text isn't markup
+                    curr_pos = start_idx + 1
+                    continue
+
+                text, url, title, url_end_idx = parsed
+                url = self.md._unhash_html_spans(url, code=True)
+            # reference anchor or reference img
+            else:
+                if not self.options.get('ref', True):
+                    curr_pos = start_idx + 1
+                    continue
+
+                parsed = self.parse_ref_anchor_or_ref_image(text, link_text, p)
+                if not parsed:
+                    curr_pos = start_idx + 1
+                    continue
+
+                text, url, title, url_end_idx = parsed
+                if url is None:
+                    # This id isn't defined, leave the markup alone.
+                    # set current pos to end of link title and continue from 
there
+                    curr_pos = p
+                    continue
+
+            # -- Encode and hash the URL and title to avoid conflicts with 
italics/bold
+
+            url = (
+                url
+                .replace('*', self.md._escape_table['*'])
+                .replace('_', self.md._escape_table['_'])
+            )
+            if title:
+                title = (
+                    _xml_escape_attr(title)
+                    .replace('*', self.md._escape_table['*'])
+                    .replace('_', self.md._escape_table['_'])
+                )
+                title_str = f' title="{title}"'
+            else:
+                title_str = ''
+
+            # -- Process the anchor/image
+
+            is_img = start_idx > 0 and text[start_idx-1] == "!"
+            if is_img:
+                if 'img' not in self.options.get('tags', ['img']):
+                    curr_pos = start_idx + 1
+                    continue
+
+                start_idx -= 1
+                result, skip = self.process_image(url, title_str, link_text)
+            elif start_idx >= anchor_allowed_pos:
+                if 'a' not in self.options.get('tags', ['a']):
+                    curr_pos = start_idx + 1
+                    continue
+
+                result, skip = self.process_anchor(url, title_str, link_text)
+            else:
+                # anchor not allowed here/invalid markup
+                curr_pos = start_idx + 1
+                continue
+
+            if "smarty-pants" in self.md.extras:
+                result = result.replace('"', self.md._escape_table['"'])
+
+            # <img> allowed from curr_pos onwards, <a> allowed from 
anchor_allowed_pos onwards.
+            # this means images can exist within `<a>` tags but anchors can 
only come after the
+            # current anchor has been closed
+            curr_pos = start_idx + skip
+            anchor_allowed_pos = start_idx + len(result)
+            text = text[:start_idx] + result + text[url_end_idx:]
+
+        return text
+
+    def test(self, text):
+        return '(' in text or '[' in text
+
+
 # User facing extras
 # ----------------------------------------------------------
 
@@ -3085,6 +3232,48 @@
         return True
 
 
+class _MarkdownFileLinksExtraOpts(_LinkProcessorExtraOpts, total=False):
+    '''Options for the `MarkdownFileLinks` extra'''
+    link_defs: bool
+    '''Whether to convert link definitions as well. Default: True'''
+
+
+class MarkdownFileLinks(LinkProcessor):
+    '''
+    Replace links to `.md` files with `.html` links
+    '''
+
+    name = 'markdown-file-links'
+    order = (Stage.LINKS,), (Stage.LINK_DEFS,)
+    options: _MarkdownFileLinksExtraOpts
+
+    def __init__(self, md: Markdown, options: Optional[dict]):
+        # override LinkProcessor defaults
+        options = {'tags': ['a'], 'ref': False, **(options or {})}
+        super().__init__(md, options)
+
+    def parse_inline_anchor_or_image(self, text: str, _link_text: str, 
start_idx: int):
+        result = super().parse_inline_anchor_or_image(text, _link_text, 
start_idx)
+        if not result or not result[1] or not result[1].endswith('.md'):
+            # return None for invalid markup, or links that don't end with 
'.md'
+            # so that we don't touch them, and other extras can process them 
freely
+            return
+        url = result[1].removesuffix('.md') + '.html'
+        return result[0], url, *result[2:]
+
+    def run(self, text: str):
+        if Stage.LINKS > self.md.order > Stage.LINK_DEFS and 
self.options.get('link_defs', True):
+            # running just after link defs have been stripped
+            for key, url in self.md.urls.items():
+                if url.endswith('.md'):
+                    self.md.urls[key] = url.removesuffix('.md') + '.html'
+
+        return super().run(text)
+
+    def test(self, text):
+        return super().test(text) and '.md' in text
+
+
 class Mermaid(FencedCodeBlocks):
     name = 'mermaid'
     order = (FencedCodeBlocks,), ()
@@ -3104,7 +3293,7 @@
     name = 'middle-word-em'
     order = (CodeFriendly,), (Stage.ITALIC_AND_BOLD,)
 
-    def __init__(self, md: Markdown, options: Union[dict, bool]):
+    def __init__(self, md: Markdown, options: Union[dict, bool, None]):
         '''
         Args:
             md: the markdown instance
@@ -3115,6 +3304,8 @@
         '''
         if isinstance(options, bool):
             options = {'allowed': options}
+        else:
+            options = options or {}
         options.setdefault('allowed', True)
         super().__init__(md, options)
 
@@ -3385,7 +3576,7 @@
         return table_re.sub(self.sub, text)
 
     def sub(self, match: re.Match) -> str:
-        trim_space_re = '^[ \t\n]+|[ \t\n]+$'
+        trim_space_re = r'^\s+|\s+$'
         trim_bar_re = r'^\||\|$'
         split_bar_re = r'^\||(?<![\`\\])\|'
         escape_bar_re = r'\\\|'
@@ -3583,6 +3774,7 @@
 Latex.register()
 LinkPatterns.register()
 MarkdownInHTML.register()
+MarkdownFileLinks.register()
 MiddleWordEm.register()
 Mermaid.register()
 Numbering.register()
@@ -3909,8 +4101,6 @@
         .replace('<', '&lt;')
         .replace('>', '&gt;'))
     if safe_mode:
-        if charset != 'base64':
-            escaped = escaped.replace('+', ' ')
         escaped = escaped.replace("'", "&#39;")
     return escaped
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/test/test_redos.py 
new/markdown2-2.5.4/test/test_redos.py
--- old/markdown2-2.5.3/test/test_redos.py      1970-01-01 01:00:00.000000000 
+0100
+++ new/markdown2-2.5.4/test/test_redos.py      2025-07-27 18:14:04.000000000 
+0200
@@ -0,0 +1,90 @@
+import logging
+import subprocess
+import sys
+import time
+from pathlib import Path
+
+log = logging.getLogger("test")
+LIB_DIR = Path(__file__).parent.parent / "lib"
+
+
+def pull_387_example_1():
+    # https://github.com/trentm/python-markdown2/pull/387
+    return "[#a" + " " * 3456
+
+
+def pull_387_example_2():
+    # https://github.com/trentm/python-markdown2/pull/387
+    return "```" + "\n" * 3456
+
+
+def pull_387_example_3():
+    # https://github.com/trentm/python-markdown2/pull/387
+    return "-*-" + " " * 3456
+
+
+def pull_402():
+    # https://github.com/trentm/python-markdown2/pull/402
+    return " " * 100_000 + "$"
+
+
+def issue493():
+    # https://github.com/trentm/python-markdown2/issues/493
+    return "**_" + "*_" * 38730 * 10 + "\x00"
+
+
+def issue_633():
+    # https://github.com/trentm/python-markdown2/issues/633
+    return '<p m="1"' * 2500 + " " * 5000 + "</div"
+
+
+# whack everything in a dict for easy lookup later on
+CASES = {
+    fn.__name__: fn
+    for fn in [
+        pull_387_example_1,
+        pull_387_example_2,
+        pull_387_example_3,
+        pull_402,
+        issue493,
+        issue_633,
+    ]
+}
+
+
+if __name__ == "__main__":
+    logging.basicConfig()
+
+    if "--execute" in sys.argv:
+        testcase = CASES[sys.argv[sys.argv.index("--execute") + 1]]
+        sys.path.insert(0, str(LIB_DIR))
+        from markdown2 import markdown
+
+        markdown(testcase())
+        sys.exit(0)
+
+    print("-- ReDoS tests")
+
+    fails = []
+    start_time = time.time()
+    for testcase in CASES:
+        print(f"markdown2/redos/{testcase} ... ", end="")
+
+        testcase_start_time = time.time()
+        try:
+            subprocess.run([sys.executable, __file__, "--execute", testcase], 
timeout=3)
+        except subprocess.TimeoutExpired:
+            fails.append(testcase)
+            print(f"FAIL ({time.time() - testcase_start_time:.3f}s)")
+        else:
+            print(f"ok ({time.time() - testcase_start_time:.3f}s)")
+
+    
print("----------------------------------------------------------------------")
+    print(f"Ran {len(CASES)} tests in {time.time() - start_time:.3f}s")
+
+    if fails:
+        print("FAIL:", fails)
+    else:
+        print("OK")
+
+    sys.exit(len(fails))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/markdown2-2.5.3/test/testall.py 
new/markdown2-2.5.4/test/testall.py
--- old/markdown2-2.5.3/test/testall.py 2024-12-11 22:59:09.000000000 +0100
+++ new/markdown2-2.5.4/test/testall.py 2025-05-18 21:25:25.000000000 +0200
@@ -17,7 +17,7 @@
     assert ' ' not in python
     o = os.popen('''%s -c "import sys; print(sys.version)"''' % python)
     ver_str = o.read().strip()
-    ver_bits = re.split(r"\.|[^\d]", ver_str, 2)[:2]
+    ver_bits = re.split(r"\.|[^\d]", ver_str, maxsplit=2)[:2]
     ver = tuple(map(int, ver_bits))
     return ver
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.html 
new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.html
--- old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.html  
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.html  
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<p>&lt;x&gt;&lt;img src=x onerror=alert("xss")//>&lt;x&gt;</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.opts 
new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.opts
--- old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.opts  
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.opts  
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{'safe_mode': 'escape'}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.text 
new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.text
--- old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.text  
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.text  
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<x><img src=x onerror=alert("xss")//><x>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_issue426.html 
new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_issue426.html
--- old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_issue426.html  
2023-06-22 23:05:35.000000000 +0200
+++ new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_issue426.html  
2025-05-18 21:25:25.000000000 +0200
@@ -15,7 +15,7 @@
 <li><p><code>ContextMixin</code> defines the method 
<code>get_context_data</code>:</p>
 
 <div class="codehilite">
-<pre><span></span><code><span class="k">def</span> <span 
class="nf">get_context_data</span><span class="p">(</span><span 
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span 
class="n">kwargs</span><span class="p">):</span>
+<pre><span></span><code><span class="k">def</span><span class="w"> 
</span><span class="nf">get_context_data</span><span class="p">(</span><span 
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span 
class="n">kwargs</span><span class="p">):</span>
     <span class="n">kwargs</span><span class="o">.</span><span 
class="n">setdefault</span><span class="p">(</span><span 
class="s1">&#39;view&#39;</span><span class="p">,</span> <span 
class="bp">self</span><span class="p">)</span>
     <span class="k">if</span> <span class="bp">self</span><span 
class="o">.</span><span class="n">extra_context</span> <span 
class="ow">is</span> <span class="ow">not</span> <span 
class="kc">None</span><span class="p">:</span>
         <span class="n">kwargs</span><span class="o">.</span><span 
class="n">update</span><span class="p">(</span><span 
class="bp">self</span><span class="o">.</span><span 
class="n">extra_context</span><span class="p">)</span>
@@ -26,7 +26,7 @@
 <p>So when overriding one must be careful to extends <code>super</code>'s 
<code>kwargs</code>:</p>
 
 <div class="codehilite">
-<pre><span></span><code><span class="k">def</span> <span 
class="nf">get_context_data</span><span class="p">(</span><span 
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span 
class="n">kwargs</span><span class="p">):</span>
+<pre><span></span><code><span class="k">def</span><span class="w"> 
</span><span class="nf">get_context_data</span><span class="p">(</span><span 
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span 
class="n">kwargs</span><span class="p">):</span>
     <span class="n">kwargs</span> <span class="o">=</span> <span 
class="nb">super</span><span class="p">()</span><span class="o">.</span><span 
class="n">get_context_data</span><span class="p">(</span><span 
class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
     <span class="n">kwargs</span><span class="p">[</span><span 
class="s1">&#39;page_title&#39;</span><span class="p">]</span> <span 
class="o">=</span> <span class="s2">&quot;Documentation&quot;</span>
     <span class="k">return</span> <span class="n">kwargs</span>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_syntax_indentation.html 
new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_syntax_indentation.html
--- 
old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_syntax_indentation.html    
    2023-06-22 23:05:35.000000000 +0200
+++ 
new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_syntax_indentation.html    
    2025-05-18 21:25:25.000000000 +0200
@@ -1,5 +1,5 @@
 <div class="codehilite">
-<pre><span></span><code><span class="k">def</span> <span 
class="nf">foo</span><span class="p">():</span>
+<pre><span></span><code><span class="k">def</span><span class="w"> 
</span><span class="nf">foo</span><span class="p">():</span>
     <span class="nb">print</span> <span class="s2">&quot;foo&quot;</span>
 
     <span class="nb">print</span> <span class="s2">&quot;bar&quot;</span>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/markdown_file_links.html 
new/markdown2-2.5.4/test/tm-cases/markdown_file_links.html
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links.html  1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links.html  2025-05-18 
21:25:25.000000000 +0200
@@ -0,0 +1,3 @@
+<p><a href="./file.html">This is a link to a markdown file</a></p>
+
+<p><a href="./something.html">This is a reference to a markdown file 
link</a></p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/markdown_file_links.opts 
new/markdown2-2.5.4/test/tm-cases/markdown_file_links.opts
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links.opts  1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links.opts  2025-05-18 
21:25:25.000000000 +0200
@@ -0,0 +1 @@
+{'extras': ['markdown-file-links']}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/markdown_file_links.text 
new/markdown2-2.5.4/test/tm-cases/markdown_file_links.text
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links.text  1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links.text  2025-05-18 
21:25:25.000000000 +0200
@@ -0,0 +1,6 @@
+[This is a link to a markdown file](./file.md)
+
+[This is a reference to a markdown file link][]
+
+
+[This is a reference to a markdown file link]: ./something.md
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.html 
new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.html
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.html      
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.html      
2025-05-18 21:25:25.000000000 +0200
@@ -0,0 +1 @@
+<p><a href="./something.md">This is a reference to a markdown file link</a> 
but link definition swapping is disabled</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.opts 
new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.opts
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.opts      
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.opts      
2025-05-18 21:25:25.000000000 +0200
@@ -0,0 +1 @@
+{'extras': {'markdown-file-links': {'link_defs': False}}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.text 
new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.text
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.text      
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.text      
2025-05-18 21:25:25.000000000 +0200
@@ -0,0 +1,4 @@
+[This is a reference to a markdown file link][] but link definition swapping 
is disabled
+
+
+[This is a reference to a markdown file link]: ./something.md
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.html 
new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.html
--- old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.html      
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.html      
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<p>abc<em>def</em>ghi</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.opts 
new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.opts
--- old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.opts      
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.opts      
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{'extras': {'middle-word-em': None}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.text 
new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.text
--- old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.text      
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.text      
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+abc_def_ghi
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.html 
new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.html
--- old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.html   1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.html   2025-05-18 
22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<p><a 
href="https://chromium.googlesource.com/v8/v8.git/+/refs/heads/beta";>Chromium</a></p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.opts 
new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.opts
--- old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.opts   1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.opts   2025-05-18 
22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{'safe_mode': 'escape'}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.text 
new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.text
--- old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.text   1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.text   2025-05-18 
22:02:26.000000000 +0200
@@ -0,0 +1 @@
+[Chromium](https://chromium.googlesource.com/v8/v8.git/+/refs/heads/beta)
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.html 
new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.html
--- old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.html        
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.html        
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1,15 @@
+<p>Some HTML5 block level elements that should be hashed so that smarty-pants 
doesn&#8217;t interfere</p>
+
+<address>
+  <a href="mailto:[email protected]";>[email protected]</a><br />
+  <a href="tel:+14155550132">+1 (415) 555‑0132</a>
+</address>
+
+<canvas width="120" height="120">
+  An alternative text describing what your canvas displays.
+</canvas>
+
+<video height="640" width="382" controls loop>
+    <source src="images/door.mp4"  type="video/mp4"/>
+    Test
+</video>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.opts 
new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.opts
--- old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.opts        
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.opts        
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{"extras": ["smarty-pants"]}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.text 
new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.text
--- old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.text        
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.text        
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1,15 @@
+Some HTML5 block level elements that should be hashed so that smarty-pants 
doesn't interfere
+
+<address>
+  <a href="mailto:[email protected]";>[email protected]</a><br />
+  <a href="tel:+14155550132">+1 (415) 555‑0132</a>
+</address>
+
+<canvas width="120" height="120">
+  An alternative text describing what your canvas displays.
+</canvas>
+
+<video height="640" width="382" controls loop>
+    <source src="images/door.mp4"  type="video/mp4"/>
+    Test
+</video>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.html 
new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.html
--- old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.html 1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.html 2025-07-27 
18:14:04.000000000 +0200
@@ -0,0 +1,14 @@
+<table>
+<thead>
+<tr>
+  <th>A</th>
+  <th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+  <td>Foo</td>
+  <td>123</td>
+</tr>
+</tbody>
+</table>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.opts 
new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.opts
--- old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.opts 1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.opts 2025-07-27 
18:14:04.000000000 +0200
@@ -0,0 +1 @@
+{'extras': ['tables']}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.text 
new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.text
--- old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.text 1970-01-01 
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.text 2025-07-27 
18:14:04.000000000 +0200
@@ -0,0 +1,3 @@
+| A   | B   |
+|-----|-----| 
+| Foo | 123 |
\ No newline at end of file

Reply via email to