File size: 5,227 Bytes
46df5f0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
"""
Equation formatting checker.
Validates:
- Punctuation after equations (based on grammar)
- Equation numbering consistency
- Variable definitions
"""
import re
from typing import List, Set
from .base import BaseChecker, CheckResult, CheckSeverity
class EquationChecker(BaseChecker):
"""Check equation formatting and consistency."""
name = "equation"
display_name = "Equations"
description = "Check equation formatting and punctuation"
# Equation environments
EQUATION_ENVS = [
'equation', 'align', 'gather', 'multline', 'eqnarray',
'equation*', 'align*', 'gather*', 'multline*', 'eqnarray*'
]
def check(self, tex_content: str, config: dict = None) -> List[CheckResult]:
results = []
# Check equation punctuation
punct_results = self._check_equation_punctuation(tex_content)
results.extend(punct_results)
# Check for numbered vs unnumbered consistency
numbering_results = self._check_numbering_consistency(tex_content)
results.extend(numbering_results)
# Check inline math consistency ($...$ vs \(...\))
inline_results = self._check_inline_math_consistency(tex_content)
results.extend(inline_results)
return results
def _check_equation_punctuation(self, content: str) -> List[CheckResult]:
"""Check if equations end with appropriate punctuation."""
results = []
for env in self.EQUATION_ENVS:
if '*' in env:
env_escaped = env.replace('*', r'\*')
else:
env_escaped = env
# Find equation content
pattern = re.compile(
rf'\\begin\{{{env_escaped}\}}(.*?)\\end\{{{env_escaped}\}}',
re.DOTALL
)
for match in pattern.finditer(content):
eq_content = match.group(1).strip()
# Check what comes after the equation
after_pos = match.end()
after_text = content[after_pos:after_pos + 50].strip()
# Equations in running text should have punctuation
# Check if equation content ends with punctuation
eq_content_clean = re.sub(r'\\label\{[^}]+\}', '', eq_content).strip()
if eq_content_clean and not re.search(r'[.,;]$', eq_content_clean):
# Check if next text starts lowercase (indicating sentence continues)
if after_text and after_text[0].islower():
line_num = self._find_line_number(content, match.end())
results.append(self._create_result(
passed=False,
severity=CheckSeverity.INFO,
message="Equation may need punctuation (sentence continues after)",
line_number=line_num,
suggestion="Add comma or period inside equation if it ends a clause"
))
return results
def _check_numbering_consistency(self, content: str) -> List[CheckResult]:
"""Check for mixed numbered and unnumbered equations."""
results = []
# Count numbered vs unnumbered
numbered = 0
unnumbered = 0
for env in self.EQUATION_ENVS:
count = len(re.findall(rf'\\begin\{{{env}\}}', content))
if '*' in env or 'nonumber' in content:
unnumbered += count
else:
numbered += count
# Also count \nonumber and \notag usage
unnumbered += len(re.findall(r'\\nonumber|\\notag', content))
# If there's a significant mix, warn
total = numbered + unnumbered
if total > 3 and numbered > 0 and unnumbered > 0:
ratio = min(numbered, unnumbered) / total
if ratio > 0.2: # More than 20% in minority
results.append(self._create_result(
passed=False,
severity=CheckSeverity.INFO,
message=f"Mixed equation numbering: {numbered} numbered, {unnumbered} unnumbered",
suggestion="Consider consistent numbering strategy"
))
return results
def _check_inline_math_consistency(self, content: str) -> List[CheckResult]:
"""Check for mixed inline math delimiters."""
results = []
# Count different inline math styles
dollar_count = len(re.findall(r'(?<!\$)\$(?!\$)[^$]+\$(?!\$)', content))
paren_count = len(re.findall(r'\\\(.*?\\\)', content))
if dollar_count > 0 and paren_count > 0:
results.append(self._create_result(
passed=False,
severity=CheckSeverity.INFO,
message=f"Mixed inline math: ${dollar_count} \\$...\\$ and {paren_count} \\(...\\)",
suggestion="Use consistent inline math delimiters throughout"
))
return results
|