diff --git a/.changeset/thin-houses-provide.md b/.changeset/thin-houses-provide.md new file mode 100644 index 000000000..e0f254566 --- /dev/null +++ b/.changeset/thin-houses-provide.md @@ -0,0 +1,5 @@ +--- +'myst-transforms': minor +--- + +Sort (frontmatter) abbreviations definitions by length diff --git a/docs/glossaries-and-terms.md b/docs/glossaries-and-terms.md index 93c9ef698..b5384f5a3 100644 --- a/docs/glossaries-and-terms.md +++ b/docs/glossaries-and-terms.md @@ -70,5 +70,5 @@ The abbreviations are case-sensitive and will replace all instances[^1] in your :::{tip} Order of Abbreviations :class: dropdown -The order of abbreviations in your frontmatter should specify the longer abbreviations first (e.g. `RHR`) and then the shorter abbreviations (e.g. `HR`). If you do this in the opposite order, you will have an abbreviation for R`HR`. +Abbreviations defined in your frontmatter are applied in longest-sorted order. If you have two abbreviations with the same suffix (e.g. `RHR` and `HR`), the longer abbreviation will always take precedence. ::: diff --git a/packages/myst-transforms/src/abbreviations.ts b/packages/myst-transforms/src/abbreviations.ts index 0cdf4851d..44c50b422 100644 --- a/packages/myst-transforms/src/abbreviations.ts +++ b/packages/myst-transforms/src/abbreviations.ts @@ -26,6 +26,7 @@ function replaceText(mdast: GenericParent, opts: Options) { const replacements: FindAndReplaceSchema = Object.fromEntries( Object.entries(opts.abbreviations) .filter(([abbr]) => abbr.length > 1) // We can't match on single characters! + .sort((a, b) => b[0].length - a[0].length || a[0].localeCompare(b[0])) // Sort by length (longest-first) then locale-ordering .map(([abbr, title]) => [ abbr, (value: any, { stack }: RegExpMatchObject) => { @@ -41,6 +42,9 @@ function replaceText(mdast: GenericParent, opts: Options) { export function abbreviationTransform(mdast: GenericParent, opts?: Options) { if (!opts?.abbreviations || Object.keys(opts.abbreviations).length === 0) return; + + // Inline abbreviations have lower priority to passed-in abbreviations + // So, we replace conflicting titles with those that have been passed-in const abbreviations = selectAll('abbreviation', mdast) as Abbreviation[]; abbreviations.forEach((node) => { if (node.title) return; @@ -48,6 +52,8 @@ export function abbreviationTransform(mdast: GenericParent, opts?: Options) { const title = opts.abbreviations?.[abbr]; if (title) node.title = title; }); + + // Replace instances of abbreviated constructs with their titles replaceText(mdast, opts); if (opts.firstTimeLong) {