Repository: Python-Markdown/markdown

Issue:
Abbreviation extension should not inject markup in mathjax contexts
Assume we have defined an abbreviation

```
*[YAFR]: Yet Another Feature Request
```

Then the following markdown

```markdown
Our function $f_\text{YAFR}$ does some things
```

will be expanded to (something like):

```
Our function $f_\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}$ does some things
```

This will cause a maths renderer like [mathjax](https://www.mathjax.org/) to fail. It is actually rather common to add subindices to notation in maths that refer to abbreviations defined in the text, so I think it would be nice to have the extension not try to expand the text.

Hints from issue comments:
@mitya57: Are you using any extension for rendering math, such as [arithmatex](https://facelessuser.github.io/pymdown-extensions/extensions/arithmatex/) or [python-markdown-math](https://github.com/mitya57/python-markdown-math/)?

These extensions should take care that math does not interfere with other markup. If you are not using them, then Markdown does not know that `$` means start or end of math block, and does not apply any special treatment to it.@facelessuser: Yes, Markdown doesn't know anything about MathJax or LaTeX-style syntax, and consequently doesn't care about expectations related to them. As @mitya57 mentioned, you must identify and protect such syntax from the Markdown parser via an extension if you care to preserve them for some other parser.@mdbenito: > Are you using any extension for rendering math, such as [arithmatex](https://facelessuser.github.io/pymdown-extensions/extensions/arithmatex/) or [python-markdown-math](https://github.com/mitya57/python-markdown-math/)?

Yes, [we are using](https://github.com/aai-institute/pyDVL/blob/develop/mkdocs.yml) arithmatex, with the default config as recommended by mkdocs-material. I'm looking at diffs between our [docs for v.0.9.2 without the error](https://pydvl.org/0.9.2/value/classwise-shapley/#dataset-pruning-for-logistic-regression-point-removal) and those [at develop with the error](https://pydvl.org/devel/value/classwise-shapley/#dataset-pruning-for-logistic-regression-point-removal) and I can't spot the problem at first sight.

In both cases, the math is surrounded by `<div class="arithmatex">` but in one it's rendered and in the other not (and contains the expanded abbreviation).

The `markdown_extensions` config hasn't changed at all, neither has mathjax's. I'm afraid I'm in for a grueling git bisect session if I want to get to the root of this :/ 

However I have worked around the issue by changing the text, so I don't know when I'll look into it.@facelessuser: @mdbenito If you have an issue with Arithmatex, please create a Discussion over at: https://github.com/facelessuser/pymdown-extensions. Please provide specific, reproducible examples. I suspect this might be a user error, but without real examples, it is hard to say.@mitya57: It looks like I can reproduce this with both arithmatex and python-markdown-math:

```python
>>> import markdown
>>> text = """
... Our function $f_\\text{YAFR}$ does some things
... 
... *[YAFR]: Yet Another Feature Request
... """
>>> markdown.markdown(text, extensions=["pymdownx.arithmatex", "abbr"])
'<p>Our function <span class="arithmatex"><span class="MathJax_Preview">f_\\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}</span><script type="math/tex">f_\\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}</script></span> does some things</p>'
>>> markdown.markdown(text, extensions=["mdx_math", "abbr"], extension_configs={"mdx_math": {"enable_dollar_delimiter": True}})
'<p>Our function <script type="math/tex">f_\\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}</script> does some things</p>'
```@facelessuser: Oh, it looks like I didn't read too close as there is a reproducible example in the opening post 🤦🏻. And yep, it is reproducible. But this is not a Python Markdown issue.

It seems that maybe these extensions need to run earlier to avoid such issues.@facelessuser: I've created an issue for Arithmatex that I can look into when I have a chance.@facelessuser: Maybe there is a bug in Python Markdown. I'm getting the feeling that Abbr is not respecting Atomic strings.

Here is an example with block and inline math:

```py
import markdown
text = """
Our function $f_\\text{YAFR}$ does some things

$$
f_\\text{YAFR}
$$

*[YAFR]: Yet Another Feature Request
"""
print(markdown.markdown(text, extensions=["pymdownx.arithmatex", "abbr"]))
```

We can see that Arithmatex intercepts them with the debug statements without abbreviations:

```console
➜  pymdown-extensions git:(main) ✗ python3 example.py
MATH DEBUG:
f_\text{YAFR}

MATH DEBUG f_\text{YAFR}
<p>Our function <span class="arithmatex"><span class="MathJax_Preview">f_\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}</span><script type="math/tex">f_\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}</script></span> does some things</p>
<div class="arithmatex">
<div class="MathJax_Preview">
f_\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}
</div>
<script type="math/tex; mode=display">
f_\text{<abbr title="Yet Another Feature Request">YAFR</abbr>}
</script>
</div>
```@facelessuser: I've confirmed that this is a bug in abbr, not in the math extensions. I also have a fix.

```console
MATH DEBUG:
f_\text{YAFR}

MATH DEBUG f_\text{YAFR}
<p>Our function <span class="arithmatex"><span class="MathJax_Preview">f_\text{YAFR}</span><script type="math/tex">f_\text{YAFR}</script></span> does some things</p>
<div class="arithmatex">
<div class="MathJax_Preview">
f_\text{YAFR}
</div>
<script type="math/tex; mode=display">
f_\text{YAFR}
</script>
</div>
```@facelessuser: I can replicate this bug without external dependencies:

```py
import markdown
text = """
This <https://this.com/{YAFR}>

*[YAFR]: Yet Another Feature Request
"""
print(markdown.markdown(text, extensions=["abbr"]))
```

```html
<p>This <a href="https://this.com/{YAFR}">https://this.com/{<abbr title="Yet Another Feature Request">YAFR</abbr>}</a></p>
```

Solution patch:
diff --git a/.pyspelling.yml b/.pyspelling.yml
index bede971..62ad026 100644
--- a/.pyspelling.yml
+++ b/.pyspelling.yml
@@ -20,6 +20,7 @@ matrix:
       - alt
       ignores:
       - 'code, pre'
+      - '.autorefs-internal[title]'
       captures:
       - '[role=main] *|*:not(script,style)'
   - pyspelling.filters.context:
diff --git a/docs/changelog.md b/docs/changelog.md
index 34817bd..29ff8d3 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 * `md_in_html` handle tags within inline code blocks better (#1075).
 * `md_in_html` fix handling of one-liner block HTML handling (#1074).
 * Ensure `<center>` is treated like a block-level element (#1481).
+* Ensure that `abbr` extension respects `AtomicString` and does not process
+  perceived abbreviations in these strings (#1512).
 
 ## [3.7] -- 2024-08-16
 
diff --git a/markdown/extensions/abbr.py b/markdown/extensions/abbr.py
index 36c1d91..ab7c374 100644
--- a/markdown/extensions/abbr.py
+++ b/markdown/extensions/abbr.py
@@ -98,20 +98,22 @@ class AbbrTreeprocessor(Treeprocessor):
         for child in reversed(el):
             self.iter_element(child, el)
         if text := el.text:
-            for m in reversed(list(self.RE.finditer(text))):
-                if self.abbrs[m.group(0)]:
-                    abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), text[m.end():])
-                    el.insert(0, abbr)
-                    text = text[:m.start()]
-            el.text = text
+            if not isinstance(text, AtomicString):
+                for m in reversed(list(self.RE.finditer(text))):
+                    if self.abbrs[m.group(0)]:
+                        abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), text[m.end():])
+                        el.insert(0, abbr)
+                        text = text[:m.start()]
+                el.text = text
         if parent is not None and el.tail:
             tail = el.tail
             index = list(parent).index(el) + 1
-            for m in reversed(list(self.RE.finditer(tail))):
-                abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), tail[m.end():])
-                parent.insert(index, abbr)
-                tail = tail[:m.start()]
-            el.tail = tail
+            if not isinstance(tail, AtomicString):
+                for m in reversed(list(self.RE.finditer(tail))):
+                    abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), tail[m.end():])
+                    parent.insert(index, abbr)
+                    tail = tail[:m.start()]
+                el.tail = tail
 
     def run(self, root: etree.Element) -> etree.Element | None:
         ''' Step through tree to find known abbreviations. '''


Test patch:
diff --git a/tests/test_syntax/extensions/test_abbr.py b/tests/test_syntax/extensions/test_abbr.py
index 9d3ebb2..32016e1 100644
--- a/tests/test_syntax/extensions/test_abbr.py
+++ b/tests/test_syntax/extensions/test_abbr.py
@@ -30,6 +30,18 @@ class TestAbbr(TestCase):
 
     default_kwargs = {'extensions': ['abbr']}
 
+    def test_ignore_atomic(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                """
+                This <https://example.com/{YAFR}>
+
+                *[YAFR]: Yet Another Feature Request
+                """
+            ),
+            '<p>This <a href="https://example.com/{YAFR}">https://example.com/{YAFR}</a></p>'
+        )
+
     def test_abbr_upper(self):
         self.assertMarkdownRenders(
             self.dedent(