Skip to content

Commit

Permalink
Added a configuration option to abbr and
Browse files Browse the repository at this point in the history
handling for empty abbreviations.

- Configuration option `use_last_abbr` allows using
the first instance of a term rather than the last.
This is useful for sites that auto-append a glossary
to pages.
- If an abbreviation does not have a definition it
will skip the term instead of writing abbr tags with
an empty title.
  • Loading branch information
nbanyan committed May 23, 2024
1 parent 37922a7 commit 9fed7aa
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 10 deletions.
14 changes: 13 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,19 @@ A call to `Markdown.reset()` now clears all previously defined abbreviations.

Abbreviations are now sorted by length before executing `AbbrTreeprocessor`
to ensure that multi-word abbreviations are implemented even if an abbreviation
exists for one of those component words.
exists for one of those component words. (#1465)

Added an optional `use_last_abbr` configuration option to the abbreviations
extension. Default (`True`) maintains the existing behavior. `False` causes
the extension to only use the first instance of an abbreviation, rather than
the last.

Empty abbreviations are now skipped by `AbbrTreeprocessor`. This avoids applying
abbr tags to text without a title value. This also allows disabling an
abbreviation, which may be useful for documents that uses two terms with
identical abbreviations.



### Fixed

Expand Down
5 changes: 5 additions & 0 deletions docs/extensions/abbreviations.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
title: Abbreviations Extension

ABBR

*[ABBR]: Abbreviation
*[ABBR]: Override Ignored

Abbreviations
=============

Expand Down
28 changes: 20 additions & 8 deletions markdown/extensions/abbr.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from __future__ import annotations

from . import Extension
from ..util import parseBoolValue
from ..blockprocessors import BlockProcessor
from ..inlinepatterns import InlineProcessor
from ..treeprocessors import Treeprocessor
Expand All @@ -41,6 +42,14 @@ class AbbrExtension(Extension):

def __init__(self, **kwargs):
""" Initiate Extension and set up configs. """
self.config = {
'use_last_abbr': [
True,
'True to use the last instance of an abbreviation, rather than the first instance.'
'Default: `True`.'
],
}
""" Default configuration options. """
super().__init__(**kwargs)
self.abbrs = {}

Expand All @@ -52,7 +61,7 @@ def extendMarkdown(self, md):
""" Insert `AbbrTreeprocessor` and `AbbrBlockprocessor`. """
md.registerExtension(self)
md.treeprocessors.register(AbbrTreeprocessor(md, self.abbrs), 'abbr', 7)
md.parser.blockprocessors.register(AbbrBlockprocessor(md.parser, self.abbrs), 'abbr', 16)
md.parser.blockprocessors.register(AbbrBlockprocessor(md.parser, self.abbrs, self.getConfigs()), 'abbr', 16)


class AbbrTreeprocessor(Treeprocessor):
Expand All @@ -69,11 +78,12 @@ def iter_element(self, el: etree.Element, parent: etree.Element | None = None) -
self.iter_element(child, el)
if text := el.text:
for m in reversed(list(self.RE.finditer(text))):
abbr = etree.Element('abbr', {'title': self.abbrs[m.group(0)]})
abbr.text = AtomicString(m.group(0))
abbr.tail = text[m.end():]
el.insert(0, abbr)
text = text[:m.start()]
if self.abbrs[m.group(0)]:
abbr = etree.Element('abbr', {'title': self.abbrs[m.group(0)]})
abbr.text = AtomicString(m.group(0))
abbr.tail = text[m.end():]
el.insert(0, abbr)
text = text[:m.start()]
el.text = text
if parent and el.tail:
tail = el.tail
Expand Down Expand Up @@ -104,8 +114,9 @@ class AbbrBlockprocessor(BlockProcessor):

RE = re.compile(r'^[*]\[(?P<abbr>[^\\]*?)\][ ]?:[ ]*\n?[ ]*(?P<title>.*)$', re.MULTILINE)

def __init__(self, parser: BlockParser, abbrs: dict):
def __init__(self, parser: BlockParser, abbrs: dict, config: dict[str, Any]):
self.abbrs: dict = abbrs
self.use_last_abbr: bool = parseBoolValue(config["use_last_abbr"])
super().__init__(parser)

def test(self, parent: etree.Element, block: str) -> bool:
Expand All @@ -122,7 +133,8 @@ def run(self, parent: etree.Element, blocks: list[str]) -> bool:
if m:
abbr = m.group('abbr').strip()
title = m.group('title').strip()
self.abbrs[abbr] = title
if self.use_last_abbr or abbr not in self.abbrs:
self.abbrs[abbr] = title
if block[m.end():].strip():
# Add any content after match back to blocks as separate block
blocks.insert(0, block[m.end():].lstrip('\n'))
Expand Down
41 changes: 40 additions & 1 deletion tests/test_syntax/extensions/test_abbr.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,26 @@ def test_abbr_override(self):
"""
<p><abbr title="The override">ABBR</abbr></p>
"""
)
),
extensions=[AbbrExtension(use_last_abbr=True)]
)

def test_abbr_override_Ignored(self):
self.assertMarkdownRenders(
self.dedent(
"""
ABBR
*[ABBR]: Abbreviation
*[ABBR]: Override Ignored
"""
),
self.dedent(
"""
<p><abbr title="Abbreviation">ABBR</abbr></p>
"""
),
extensions=[AbbrExtension(use_last_abbr=False)]
)

def test_abbr_nested(self):
Expand Down Expand Up @@ -401,6 +420,26 @@ def test_abbr_superset_vs_subset(self):
)
)

def test_abbr_empty(self):
self.assertMarkdownRenders(
self.dedent(
"""
*[abbr]: Abbreviation Definition
abbr
*[abbr]:
Testing document text.
"""
),
self.dedent(
"""
<p>abbr</p>\n<p>Testing document text.</p>
"""
)
)

def test_abbr_reset(self):
ext = AbbrExtension()
md = Markdown(extensions=[ext])
Expand Down

0 comments on commit 9fed7aa

Please sign in to comment.