from fastcore.test import *helper.latex.macros_and_commands
Latex functions for identifying macros and commands (to replace)
Identify macros and commands (to replace)
The following functions were originally written for latex.formatting, but were moved here.
custom_commands
custom_commands (preamble:str)
*Return a dict mapping commands (and math operators) defined in preamble to the number of arguments display text of the commands.
Assumes that the newcommands only have at most one default parameter (newcommands with multiple default parameters are not valid in LaTeX).
Ignores all comented newcommands.*
| Type | Details | |
|---|---|---|
| preamble | str | The preamble of a LaTeX document. |
| Returns | list | Each tuple consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or None otherwise, and 4. the display text of the command. |
# Basic
text_1 = r'\newcommand{\con}{\mathcal{C}}'
test_eq(custom_commands(text_1), [('con', 0, None, r'\mathcal{C}')])
# With a parameter
text_2 = r'\newcommand{\field}[1]{\mathbb{#1}}'
test_eq(custom_commands(text_2), [('field', 1, None, r'\mathbb{#1}')])
# With multiple parameters, the first of which has a default value of `2`
text_3 = r'\newcommand{\plusbinomial}[3][2]{(#2 + #3)^#1}'
test_eq(custom_commands(text_3), [('plusbinomial', 3, '2', r'(#2 + #3)^#1')])
# The display text has backslashes `\` and curly brances `{}``
text_4 = r'\newcommand{\beq}{\begin{displaymath}}'
test_eq(custom_commands(text_4), [('beq', 0, None, '\\begin{displaymath}')])
# Basic with spaces in the newcommand declaration
text_6 = r'\newcommand {\con} {\mathcal{C}}'
test_eq(custom_commands(text_6), [('con', 0, None, r'\mathcal{C}')])
# With a parameter and spaces in the newcommand declaration
text_7 = r'\newcommand {\field} [1] {\mathbb{#1}}'
test_eq(custom_commands(text_7), [('field', 1, None, r'\mathbb{#1}')])
# With multiple parameters, a default value, and spaces in the newcommand declaration
text_8 = r'\newcommand {\plusbinomial} [3] [2] {(#2 + #3)^#1}'
test_eq(custom_commands(text_8), [('plusbinomial', 3, '2', r'(#2 + #3)^#1')])
# With a comment `%'; commented out command declarations should not be detected.
text_9 = r'% \newcommand{\con}{\mathcal{C}}'
test_eq(custom_commands(text_9), [])
# Spanning multiple lines
text_10 = r'''\newcommand{\mat}[4]{\left[\begin{array}{cc}#1 & #2 \\
#3 & #4\end{array}\right]}'''
test_eq(
custom_commands(text_10),
[('mat', 4, None,
'\\left[\\begin{array}{cc}#1 & #2 \\\\\n #3 & #4\\end{array}\\right]')])
# Math operator
text_11 = r'\DeclareMathOperator{\Hom}{Hom}'
test_eq(custom_commands(text_11), [('Hom', 0, None, 'Hom')])
text_12 = r'\DeclareMathOperator{\tConf}{\widetilde{Conf}}'
test_eq(custom_commands(text_12), [('tConf', 0, None, r'\widetilde{Conf}')])
# `\def` commands
# \def is a bit complicated because arguments can either be provided with []
# or can be provided with {}.
text_13 = r'\def\A{{\cO_{K}}}'
test_eq(custom_commands(text_13), [('A', 0, None, r'{\cO_{K}}')])
# newcommand and renewcommand don't require {} for the
# command name, cf. https://arxiv.org/abs/1703.05365
text_14 = r'\newcommand\A{{\mathbb A}}'
test_eq(custom_commands(text_14), [('A', 0, None, r'{\mathbb A}')])
# A test for https://arxiv.org/abs/0902.4637
text_15 = r'\newcommand{\til}[1]{{\widetilde{#1}}}'
test_eq(custom_commands(text_15), [('til', 1, None, '{\\widetilde{#1}}')])regex_pattern_detecting_command
regex_pattern_detecting_command (command_tuple:tuple[str,int,typing.Opti onal[str],str])
*Return a regex.pattern object (not a re.pattern object) detecting the command with the specified number of parameters, optional argument, and display text.
Assumes that the curly braces used to write the invocations of the commands are balanced and properly nested. Assumes that there are no two commands of the same name.*
| Type | Details | |
|---|---|---|
| command_tuple | tuple | Consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or None otherwise, and 4. the display text of the command. |
| Returns | Pattern |
# Basic
pattern = regex_pattern_detecting_command(('Sur', 0, None, r'\mathrm{Sur}'))
text = r'The number of element of $\Sur(\operatorname{Cl} \mathcal{O}_L, A)$ is ...'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], r'\Sur')
pattern = regex_pattern_detecting_command(('frac', 2, None, r'\mathrm{Sur}'))
text = r'\frac{\frac{2}{5}}{7}'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
pattern = regex_pattern_detecting_command(('frac', 2, None, r'\mathrm{Sur}'))
text = r'\frac{error}{7'
match = pattern.search(text)
test_is(match, None)
# start, end = match.span()
# test_eq(text[start:end], text)
pattern = regex_pattern_detecting_command(('frac', 2, None, r'\mathrm{Sur}'))
text = r'\frac{\frac{2}{5}}{7'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], r'\frac{2}{5}')
# One parameter
pattern = regex_pattern_detecting_command(('field', 1, None, r'\mathbb{#1}'))
text = r'\field{Q}'
# print(pattern.pattern)
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
# Multiple parameters
pattern = regex_pattern_detecting_command(('mat', 4, None, r'\left[\begin{array}{cc}#1 & #2 \\ #3 & #4\end{array}\right]'))
text = r'\mat{{123}}{asdfasdf{}{}}{{{}}}{{asdf}{asdf}{}}' # This is a balanced str.
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
test_eq(match.group(1), r'{123}')
# Multiple parameters, one of which is optional parameter
pattern = regex_pattern_detecting_command(('plusbinomial', 3, '2', r'(#2 + #3)^#1'))
# When the optional parameter is used
text = r'\plusbinomial{x}{y}'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
# When the optional parameter is not used
text = r'\plusbinomial[4]{x}{y}'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
# One parameter that is optional.
pattern = regex_pattern_detecting_command(('greet', 1, 'world', r'Hello #1!'))
# When the optional parameter is used
text = r'\greet'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
# When the optional parameter is not used
text = r'\greet[govna]'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], text)
# In the following example, `\del` is a command defined as `\delta`.
# Any invocation `\delta` should detected as invocations of `\del``
command_tuple = (r'del', 0, None, r'\delta')
pattern = regex_pattern_detecting_command(command_tuple)
text = r'\del should be detected.'
match = pattern.search(text)
start, end = match.span()
test_eq(text[start:end], r'\del')
text = r'\delta should not be detected.'
match = pattern.search(text)
assert match is None
# test_eq(replace_command_in_text(text, command_tuple), r'\delta should be replaced. \delta should not.')
# In the following example, the command takes one argument, but sometimes the command
# is `\del`
command_tuple = ('til', 1, None, '{\\widetilde{#1}}')
pattern = regex_pattern_detecting_command(command_tuple)
text = r'\til \calh_g'
match = pattern.search(text)
# start, end = match.span()