| | """ |
| | 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" |
| | |
| | |
| | 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 |
| | ) |
| | |
| | |
| | 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 = [] |
| | |
| | |
| | for match in self.TABLE_ENV_PATTERN.finditer(tex_content): |
| | env_content = match.group(1) |
| | env_start = match.start() |
| | |
| | |
| | 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) |
| | |
| | |
| | for match in self.FIGURE_ENV_PATTERN.finditer(tex_content): |
| | env_content = match.group(1) |
| | env_start = match.start() |
| | |
| | |
| | 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: |
| | |
| | return None |
| | |
| | |
| | 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) |
| | |
| | |
| | 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: |
| | |
| | return None |
| | |
| | |
| | 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 |
| |
|