File size: 5,289 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 136 137 |
"""
Caption placement checker.
Validates that:
- Table captions appear ABOVE the table content
- Figure captions appear BELOW the figure content
"""
import re
from typing import List
from .base import BaseChecker, CheckResult, CheckSeverity
class CaptionChecker(BaseChecker):
"""Check for correct caption placement in tables and figures."""
name = "caption"
display_name = "Caption Placement"
description = "Verify table captions are above and figure captions are below"
# Patterns for environments
TABLE_ENV_PATTERN = re.compile(
r'\\begin\{table\*?\}(.*?)\\end\{table\*?\}',
re.DOTALL | re.IGNORECASE
)
FIGURE_ENV_PATTERN = re.compile(
r'\\begin\{figure\*?\}(.*?)\\end\{figure\*?\}',
re.DOTALL | re.IGNORECASE
)
# Content patterns
CAPTION_PATTERN = re.compile(r'\\caption\s*[\[{]')
TABULAR_PATTERN = re.compile(r'\\begin\{tabular')
INCLUDEGRAPHICS_PATTERN = re.compile(r'\\includegraphics')
TIKZ_PATTERN = re.compile(r'\\begin\{tikzpicture\}')
def check(self, tex_content: str, config: dict = None) -> List[CheckResult]:
results = []
# Check table environments
for match in self.TABLE_ENV_PATTERN.finditer(tex_content):
env_content = match.group(1)
env_start = match.start()
# Skip if commented
if self._is_commented(tex_content, env_start):
continue
result = self._check_table_caption(env_content, tex_content, env_start)
if result:
results.append(result)
# Check figure environments
for match in self.FIGURE_ENV_PATTERN.finditer(tex_content):
env_content = match.group(1)
env_start = match.start()
# Skip if commented
if self._is_commented(tex_content, env_start):
continue
result = self._check_figure_caption(env_content, tex_content, env_start)
if result:
results.append(result)
return results
def _check_table_caption(self, env_content: str, full_content: str, env_start: int) -> CheckResult:
"""Check that table caption is above tabular content."""
caption_match = self.CAPTION_PATTERN.search(env_content)
tabular_match = self.TABULAR_PATTERN.search(env_content)
if not caption_match:
line_num = self._find_line_number(full_content, env_start)
return self._create_result(
passed=False,
severity=CheckSeverity.WARNING,
message="Table environment missing caption",
line_number=line_num,
suggestion="Add \\caption{} before \\begin{tabular}"
)
if not tabular_match:
# Table without tabular content - skip
return None
# Caption should come BEFORE tabular
if caption_match.start() > tabular_match.start():
line_num = self._find_line_number(full_content, env_start + caption_match.start())
return self._create_result(
passed=False,
severity=CheckSeverity.ERROR,
message="Table caption should be placed ABOVE the table content",
line_number=line_num,
line_content=self._get_line_content(full_content, line_num),
suggestion="Move \\caption{} before \\begin{tabular}"
)
return None
def _check_figure_caption(self, env_content: str, full_content: str, env_start: int) -> CheckResult:
"""Check that figure caption is below image content."""
caption_match = self.CAPTION_PATTERN.search(env_content)
graphics_match = self.INCLUDEGRAPHICS_PATTERN.search(env_content)
tikz_match = self.TIKZ_PATTERN.search(env_content)
# Find the actual content (either graphics or tikz)
content_match = graphics_match or tikz_match
if not caption_match:
line_num = self._find_line_number(full_content, env_start)
return self._create_result(
passed=False,
severity=CheckSeverity.WARNING,
message="Figure environment missing caption",
line_number=line_num,
suggestion="Add \\caption{} after \\includegraphics"
)
if not content_match:
# Figure without graphics/tikz - could be custom content, skip
return None
# Caption should come AFTER content
if caption_match.start() < content_match.start():
line_num = self._find_line_number(full_content, env_start + caption_match.start())
return self._create_result(
passed=False,
severity=CheckSeverity.ERROR,
message="Figure caption should be placed BELOW the figure content",
line_number=line_num,
line_content=self._get_line_content(full_content, line_num),
suggestion="Move \\caption{} after \\includegraphics"
)
return None
|