deepagents-printshop 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/content_editor/__init__.py +1 -0
- agents/content_editor/agent.py +279 -0
- agents/content_editor/content_reviewer.py +327 -0
- agents/content_editor/versioned_agent.py +455 -0
- agents/latex_specialist/__init__.py +1 -0
- agents/latex_specialist/agent.py +531 -0
- agents/latex_specialist/latex_analyzer.py +510 -0
- agents/latex_specialist/latex_optimizer.py +1192 -0
- agents/qa_orchestrator/__init__.py +1 -0
- agents/qa_orchestrator/agent.py +603 -0
- agents/qa_orchestrator/langgraph_workflow.py +733 -0
- agents/qa_orchestrator/pipeline_types.py +72 -0
- agents/qa_orchestrator/quality_gates.py +495 -0
- agents/qa_orchestrator/workflow_coordinator.py +139 -0
- agents/research_agent/__init__.py +1 -0
- agents/research_agent/agent.py +258 -0
- agents/research_agent/llm_report_generator.py +1023 -0
- agents/research_agent/report_generator.py +536 -0
- agents/visual_qa/__init__.py +1 -0
- agents/visual_qa/agent.py +410 -0
- deepagents_printshop-0.1.0.dist-info/METADATA +744 -0
- deepagents_printshop-0.1.0.dist-info/RECORD +37 -0
- deepagents_printshop-0.1.0.dist-info/WHEEL +4 -0
- deepagents_printshop-0.1.0.dist-info/entry_points.txt +2 -0
- deepagents_printshop-0.1.0.dist-info/licenses/LICENSE +86 -0
- tools/__init__.py +1 -0
- tools/change_tracker.py +419 -0
- tools/content_type_loader.py +171 -0
- tools/graph_generator.py +281 -0
- tools/latex_generator.py +374 -0
- tools/llm_latex_generator.py +678 -0
- tools/magazine_layout.py +462 -0
- tools/pattern_injector.py +250 -0
- tools/pattern_learner.py +477 -0
- tools/pdf_compiler.py +386 -0
- tools/version_manager.py +346 -0
- tools/visual_qa.py +799 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LaTeX Analyzer - Milestone 3
|
|
3
|
+
|
|
4
|
+
Analyzes LaTeX document structure, typography, and formatting quality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import os
|
|
9
|
+
from typing import Dict, List, Tuple, Optional
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class LaTeXIssue:
|
|
16
|
+
"""Represents a LaTeX formatting issue."""
|
|
17
|
+
severity: str # 'error', 'warning', 'suggestion'
|
|
18
|
+
category: str # 'structure', 'typography', 'tables', 'figures', 'general'
|
|
19
|
+
description: str
|
|
20
|
+
line_number: Optional[int] = None
|
|
21
|
+
suggestion: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class LaTeXAnalysisResult:
|
|
26
|
+
"""Results of LaTeX document analysis."""
|
|
27
|
+
structure_score: int # 0-25
|
|
28
|
+
typography_score: int # 0-25
|
|
29
|
+
tables_figures_score: int # 0-25
|
|
30
|
+
best_practices_score: int # 0-25
|
|
31
|
+
overall_score: int # 0-100
|
|
32
|
+
issues: List[LaTeXIssue]
|
|
33
|
+
document_class: str
|
|
34
|
+
packages_used: List[str]
|
|
35
|
+
structure_info: Dict
|
|
36
|
+
suggestions: List[str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LaTeXAnalyzer:
|
|
40
|
+
"""
|
|
41
|
+
Analyzes LaTeX documents for quality, structure, and formatting issues.
|
|
42
|
+
|
|
43
|
+
Features:
|
|
44
|
+
- Document structure analysis
|
|
45
|
+
- Typography quality assessment
|
|
46
|
+
- Table and figure formatting validation
|
|
47
|
+
- LaTeX best practices checking
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self):
|
|
51
|
+
"""Initialize the LaTeX analyzer."""
|
|
52
|
+
self.document_classes = {
|
|
53
|
+
'article': {'sections': ['section', 'subsection', 'subsubsection']},
|
|
54
|
+
'report': {'sections': ['chapter', 'section', 'subsection', 'subsubsection']},
|
|
55
|
+
'book': {'sections': ['part', 'chapter', 'section', 'subsection', 'subsubsection']},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
self.recommended_packages = {
|
|
59
|
+
'typography': ['fontenc', 'inputenc', 'microtype'],
|
|
60
|
+
'tables': ['booktabs', 'array', 'longtable'],
|
|
61
|
+
'figures': ['graphicx', 'float', 'caption'],
|
|
62
|
+
'references': ['hyperref', 'cite', 'natbib'],
|
|
63
|
+
'math': ['amsmath', 'amssymb', 'amsthm']
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
self.deprecated_commands = [
|
|
67
|
+
'\\bf', '\\it', '\\rm', '\\sc', '\\sf', '\\sl', '\\tt',
|
|
68
|
+
'\\centerline', '\\over', '\\above', '\\atop'
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
def analyze_document(self, latex_content: str) -> LaTeXAnalysisResult:
|
|
72
|
+
"""
|
|
73
|
+
Perform comprehensive LaTeX document analysis.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
latex_content: The LaTeX document content
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Detailed analysis results
|
|
80
|
+
"""
|
|
81
|
+
issues = []
|
|
82
|
+
|
|
83
|
+
# Analyze different aspects
|
|
84
|
+
structure_result = self._analyze_structure(latex_content)
|
|
85
|
+
typography_result = self._analyze_typography(latex_content)
|
|
86
|
+
tables_figures_result = self._analyze_tables_figures(latex_content)
|
|
87
|
+
best_practices_result = self._analyze_best_practices(latex_content)
|
|
88
|
+
|
|
89
|
+
# Combine issues
|
|
90
|
+
issues.extend(structure_result['issues'])
|
|
91
|
+
issues.extend(typography_result['issues'])
|
|
92
|
+
issues.extend(tables_figures_result['issues'])
|
|
93
|
+
issues.extend(best_practices_result['issues'])
|
|
94
|
+
|
|
95
|
+
# Calculate scores
|
|
96
|
+
structure_score = structure_result['score']
|
|
97
|
+
typography_score = typography_result['score']
|
|
98
|
+
tables_figures_score = tables_figures_result['score']
|
|
99
|
+
best_practices_score = best_practices_result['score']
|
|
100
|
+
|
|
101
|
+
overall_score = structure_score + typography_score + tables_figures_score + best_practices_score
|
|
102
|
+
|
|
103
|
+
# Extract document info
|
|
104
|
+
doc_class = self._extract_document_class(latex_content)
|
|
105
|
+
packages = self._extract_packages(latex_content)
|
|
106
|
+
|
|
107
|
+
# Generate suggestions
|
|
108
|
+
suggestions = self._generate_suggestions(issues, structure_result, typography_result)
|
|
109
|
+
|
|
110
|
+
return LaTeXAnalysisResult(
|
|
111
|
+
structure_score=structure_score,
|
|
112
|
+
typography_score=typography_score,
|
|
113
|
+
tables_figures_score=tables_figures_score,
|
|
114
|
+
best_practices_score=best_practices_score,
|
|
115
|
+
overall_score=overall_score,
|
|
116
|
+
issues=issues,
|
|
117
|
+
document_class=doc_class,
|
|
118
|
+
packages_used=packages,
|
|
119
|
+
structure_info=structure_result['info'],
|
|
120
|
+
suggestions=suggestions
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _analyze_structure(self, content: str) -> Dict:
|
|
124
|
+
"""Analyze document structure and hierarchy."""
|
|
125
|
+
issues = []
|
|
126
|
+
score = 25 # Start with full points
|
|
127
|
+
|
|
128
|
+
# Extract document class
|
|
129
|
+
doc_class = self._extract_document_class(content)
|
|
130
|
+
|
|
131
|
+
# Find all sections
|
|
132
|
+
section_patterns = [
|
|
133
|
+
(r'\\part\{([^}]+)\}', 'part'),
|
|
134
|
+
(r'\\chapter\{([^}]+)\}', 'chapter'),
|
|
135
|
+
(r'\\section\{([^}]+)\}', 'section'),
|
|
136
|
+
(r'\\subsection\{([^}]+)\}', 'subsection'),
|
|
137
|
+
(r'\\subsubsection\{([^}]+)\}', 'subsubsection'),
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
sections_found = []
|
|
141
|
+
for pattern, level in section_patterns:
|
|
142
|
+
matches = re.finditer(pattern, content)
|
|
143
|
+
for match in matches:
|
|
144
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
145
|
+
sections_found.append({
|
|
146
|
+
'level': level,
|
|
147
|
+
'title': match.group(1),
|
|
148
|
+
'line': line_num,
|
|
149
|
+
'position': match.start()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
# Sort by position in document
|
|
153
|
+
sections_found.sort(key=lambda x: x['position'])
|
|
154
|
+
|
|
155
|
+
# Check section hierarchy
|
|
156
|
+
hierarchy_issues = self._check_section_hierarchy(sections_found, doc_class)
|
|
157
|
+
issues.extend(hierarchy_issues)
|
|
158
|
+
if hierarchy_issues:
|
|
159
|
+
score -= min(10, len(hierarchy_issues) * 3)
|
|
160
|
+
|
|
161
|
+
# Check for title and author
|
|
162
|
+
if not re.search(r'\\title\{', content):
|
|
163
|
+
issues.append(LaTeXIssue(
|
|
164
|
+
severity='warning',
|
|
165
|
+
category='structure',
|
|
166
|
+
description='Document is missing \\title command',
|
|
167
|
+
suggestion='Add \\title{Your Title Here} in the preamble'
|
|
168
|
+
))
|
|
169
|
+
score -= 3
|
|
170
|
+
|
|
171
|
+
if not re.search(r'\\author\{', content):
|
|
172
|
+
issues.append(LaTeXIssue(
|
|
173
|
+
severity='suggestion',
|
|
174
|
+
category='structure',
|
|
175
|
+
description='Document is missing \\author command',
|
|
176
|
+
suggestion='Add \\author{Your Name} in the preamble'
|
|
177
|
+
))
|
|
178
|
+
score -= 2
|
|
179
|
+
|
|
180
|
+
# Check for proper document environment
|
|
181
|
+
if not re.search(r'\\begin\{document\}', content):
|
|
182
|
+
issues.append(LaTeXIssue(
|
|
183
|
+
severity='error',
|
|
184
|
+
category='structure',
|
|
185
|
+
description='Document is missing \\begin{document}',
|
|
186
|
+
suggestion='Add \\begin{document} and \\end{document}'
|
|
187
|
+
))
|
|
188
|
+
score -= 10
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
'score': max(0, score),
|
|
192
|
+
'issues': issues,
|
|
193
|
+
'info': {
|
|
194
|
+
'document_class': doc_class,
|
|
195
|
+
'sections_found': sections_found,
|
|
196
|
+
'section_count': len(sections_found)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
def _analyze_typography(self, content: str) -> Dict:
|
|
201
|
+
"""Analyze typography and formatting quality."""
|
|
202
|
+
issues = []
|
|
203
|
+
score = 25 # Start with full points
|
|
204
|
+
|
|
205
|
+
# Check for proper font encoding
|
|
206
|
+
if not re.search(r'\\usepackage\[[^]]*\]\{fontenc\}', content):
|
|
207
|
+
issues.append(LaTeXIssue(
|
|
208
|
+
severity='suggestion',
|
|
209
|
+
category='typography',
|
|
210
|
+
description='Missing font encoding package',
|
|
211
|
+
suggestion='Add \\usepackage[T1]{fontenc} for better font encoding'
|
|
212
|
+
))
|
|
213
|
+
score -= 2
|
|
214
|
+
|
|
215
|
+
# Check for input encoding
|
|
216
|
+
if not re.search(r'\\usepackage\[[^]]*\]\{inputenc\}', content):
|
|
217
|
+
issues.append(LaTeXIssue(
|
|
218
|
+
severity='suggestion',
|
|
219
|
+
category='typography',
|
|
220
|
+
description='Missing input encoding package',
|
|
221
|
+
suggestion='Add \\usepackage[utf8]{inputenc} for UTF-8 support'
|
|
222
|
+
))
|
|
223
|
+
score -= 2
|
|
224
|
+
|
|
225
|
+
# Check for microtype (better typography)
|
|
226
|
+
if not re.search(r'\\usepackage.*\{microtype\}', content):
|
|
227
|
+
issues.append(LaTeXIssue(
|
|
228
|
+
severity='suggestion',
|
|
229
|
+
category='typography',
|
|
230
|
+
description='Missing microtype package for better typography',
|
|
231
|
+
suggestion='Add \\usepackage{microtype} for improved spacing'
|
|
232
|
+
))
|
|
233
|
+
score -= 1
|
|
234
|
+
|
|
235
|
+
# Check for proper spacing issues
|
|
236
|
+
spacing_issues = [
|
|
237
|
+
(r'\s{2,}', 'Multiple consecutive spaces found'),
|
|
238
|
+
(r'[.!?]\w', 'Missing space after sentence ending'),
|
|
239
|
+
(r'\w[.!?][.!?]', 'Multiple consecutive punctuation marks'),
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
for pattern, description in spacing_issues:
|
|
243
|
+
matches = list(re.finditer(pattern, content))
|
|
244
|
+
if matches:
|
|
245
|
+
issues.append(LaTeXIssue(
|
|
246
|
+
severity='warning',
|
|
247
|
+
category='typography',
|
|
248
|
+
description=f'{description} ({len(matches)} instances)',
|
|
249
|
+
suggestion='Fix spacing issues for better typography'
|
|
250
|
+
))
|
|
251
|
+
score -= min(3, len(matches))
|
|
252
|
+
|
|
253
|
+
# Check for proper emphasis usage
|
|
254
|
+
if re.search(r'\\textit\{[^}]*\\textit\{', content):
|
|
255
|
+
issues.append(LaTeXIssue(
|
|
256
|
+
severity='warning',
|
|
257
|
+
category='typography',
|
|
258
|
+
description='Nested emphasis commands found',
|
|
259
|
+
suggestion='Avoid nesting \\textit or \\textbf commands'
|
|
260
|
+
))
|
|
261
|
+
score -= 2
|
|
262
|
+
|
|
263
|
+
# Check line length (if we can detect it)
|
|
264
|
+
lines = content.split('\n')
|
|
265
|
+
long_lines = [i+1 for i, line in enumerate(lines) if len(line) > 100 and not line.strip().startswith('%')]
|
|
266
|
+
if len(long_lines) > 5:
|
|
267
|
+
issues.append(LaTeXIssue(
|
|
268
|
+
severity='suggestion',
|
|
269
|
+
category='typography',
|
|
270
|
+
description=f'Many long lines detected ({len(long_lines)} lines > 100 chars)',
|
|
271
|
+
suggestion='Consider breaking long lines for better readability'
|
|
272
|
+
))
|
|
273
|
+
score -= 1
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
'score': max(0, score),
|
|
277
|
+
'issues': issues,
|
|
278
|
+
'info': {
|
|
279
|
+
'long_lines_count': len(long_lines),
|
|
280
|
+
'average_line_length': sum(len(line) for line in lines) / len(lines) if lines else 0
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
def _analyze_tables_figures(self, content: str) -> Dict:
|
|
285
|
+
"""Analyze table and figure formatting."""
|
|
286
|
+
issues = []
|
|
287
|
+
score = 25
|
|
288
|
+
|
|
289
|
+
# Find tables
|
|
290
|
+
table_patterns = [
|
|
291
|
+
r'\\begin\{tabular\}',
|
|
292
|
+
r'\\begin\{table\}',
|
|
293
|
+
r'\\begin\{longtable\}'
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
tables_found = 0
|
|
297
|
+
for pattern in table_patterns:
|
|
298
|
+
tables_found += len(re.findall(pattern, content))
|
|
299
|
+
|
|
300
|
+
# Find figures
|
|
301
|
+
figure_patterns = [
|
|
302
|
+
r'\\begin\{figure\}',
|
|
303
|
+
r'\\includegraphics'
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
figures_found = 0
|
|
307
|
+
for pattern in figure_patterns:
|
|
308
|
+
figures_found += len(re.findall(pattern, content))
|
|
309
|
+
|
|
310
|
+
# Check for booktabs usage in tables
|
|
311
|
+
if tables_found > 0:
|
|
312
|
+
if not re.search(r'\\usepackage.*\{booktabs\}', content):
|
|
313
|
+
issues.append(LaTeXIssue(
|
|
314
|
+
severity='suggestion',
|
|
315
|
+
category='tables',
|
|
316
|
+
description='Consider using booktabs package for professional tables',
|
|
317
|
+
suggestion='Add \\usepackage{booktabs} and use \\toprule, \\midrule, \\bottomrule'
|
|
318
|
+
))
|
|
319
|
+
score -= 3
|
|
320
|
+
|
|
321
|
+
# Check for old-style table rules
|
|
322
|
+
if re.search(r'\\hline', content) and not re.search(r'\\(top|mid|bottom)rule', content):
|
|
323
|
+
issues.append(LaTeXIssue(
|
|
324
|
+
severity='suggestion',
|
|
325
|
+
category='tables',
|
|
326
|
+
description='Using \\hline instead of booktabs rules',
|
|
327
|
+
suggestion='Replace \\hline with \\toprule, \\midrule, \\bottomrule'
|
|
328
|
+
))
|
|
329
|
+
score -= 2
|
|
330
|
+
|
|
331
|
+
# Check figure placement
|
|
332
|
+
if figures_found > 0:
|
|
333
|
+
if not re.search(r'\\usepackage.*\{float\}', content):
|
|
334
|
+
issues.append(LaTeXIssue(
|
|
335
|
+
severity='suggestion',
|
|
336
|
+
category='figures',
|
|
337
|
+
description='Consider using float package for better figure placement',
|
|
338
|
+
suggestion='Add \\usepackage{float} for better figure control'
|
|
339
|
+
))
|
|
340
|
+
score -= 1
|
|
341
|
+
|
|
342
|
+
# Check for proper figure captions
|
|
343
|
+
caption_count = len(re.findall(r'\\caption\{', content))
|
|
344
|
+
if figures_found > caption_count:
|
|
345
|
+
issues.append(LaTeXIssue(
|
|
346
|
+
severity='warning',
|
|
347
|
+
category='figures',
|
|
348
|
+
description=f'Some figures missing captions ({figures_found} figures, {caption_count} captions)',
|
|
349
|
+
suggestion='Add \\caption{...} to all figures'
|
|
350
|
+
))
|
|
351
|
+
score -= 3
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
'score': max(0, score),
|
|
355
|
+
'issues': issues,
|
|
356
|
+
'info': {
|
|
357
|
+
'tables_found': tables_found,
|
|
358
|
+
'figures_found': figures_found,
|
|
359
|
+
'caption_count': caption_count if figures_found > 0 else 0
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
def _analyze_best_practices(self, content: str) -> Dict:
|
|
364
|
+
"""Analyze LaTeX best practices compliance."""
|
|
365
|
+
issues = []
|
|
366
|
+
score = 25
|
|
367
|
+
|
|
368
|
+
# Check for deprecated commands
|
|
369
|
+
for cmd in self.deprecated_commands:
|
|
370
|
+
if cmd in content:
|
|
371
|
+
issues.append(LaTeXIssue(
|
|
372
|
+
severity='warning',
|
|
373
|
+
category='general',
|
|
374
|
+
description=f'Deprecated command {cmd} found',
|
|
375
|
+
suggestion=f'Replace {cmd} with modern equivalent'
|
|
376
|
+
))
|
|
377
|
+
score -= 2
|
|
378
|
+
|
|
379
|
+
# Check for proper package loading order
|
|
380
|
+
hyperref_pos = content.find('\\usepackage{hyperref}')
|
|
381
|
+
other_packages = re.findall(r'\\usepackage\{([^}]+)\}', content)
|
|
382
|
+
|
|
383
|
+
if hyperref_pos != -1:
|
|
384
|
+
# hyperref should generally be loaded last
|
|
385
|
+
later_packages = []
|
|
386
|
+
for match in re.finditer(r'\\usepackage\{([^}]+)\}', content[hyperref_pos:]):
|
|
387
|
+
pkg = match.group(1)
|
|
388
|
+
if pkg != 'hyperref':
|
|
389
|
+
later_packages.append(pkg)
|
|
390
|
+
|
|
391
|
+
if later_packages:
|
|
392
|
+
issues.append(LaTeXIssue(
|
|
393
|
+
severity='suggestion',
|
|
394
|
+
category='general',
|
|
395
|
+
description='hyperref package should generally be loaded last',
|
|
396
|
+
suggestion='Move \\usepackage{hyperref} to end of preamble'
|
|
397
|
+
))
|
|
398
|
+
score -= 1
|
|
399
|
+
|
|
400
|
+
# Check for proper label usage
|
|
401
|
+
labels = re.findall(r'\\label\{([^}]+)\}', content)
|
|
402
|
+
refs = re.findall(r'\\ref\{([^}]+)\}', content)
|
|
403
|
+
|
|
404
|
+
unused_labels = set(labels) - set(refs)
|
|
405
|
+
if unused_labels:
|
|
406
|
+
issues.append(LaTeXIssue(
|
|
407
|
+
severity='suggestion',
|
|
408
|
+
category='general',
|
|
409
|
+
description=f'Unused labels found: {", ".join(list(unused_labels)[:3])}{"..." if len(unused_labels) > 3 else ""}',
|
|
410
|
+
suggestion='Remove unused \\label commands'
|
|
411
|
+
))
|
|
412
|
+
score -= 1
|
|
413
|
+
|
|
414
|
+
# Check for proper math mode usage
|
|
415
|
+
inline_math_issues = re.findall(r'\$[^$]*\$\$[^$]*\$', content)
|
|
416
|
+
if inline_math_issues:
|
|
417
|
+
issues.append(LaTeXIssue(
|
|
418
|
+
severity='warning',
|
|
419
|
+
category='general',
|
|
420
|
+
description='Possible incorrect math mode usage ($ mixed with $$)',
|
|
421
|
+
suggestion='Use consistent math delimiters: $ for inline, $$ or \\[ \\] for display'
|
|
422
|
+
))
|
|
423
|
+
score -= 2
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
'score': max(0, score),
|
|
427
|
+
'issues': issues,
|
|
428
|
+
'info': {
|
|
429
|
+
'packages_count': len(other_packages),
|
|
430
|
+
'labels_count': len(labels),
|
|
431
|
+
'refs_count': len(refs),
|
|
432
|
+
'unused_labels': list(unused_labels)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
def _extract_document_class(self, content: str) -> str:
|
|
437
|
+
"""Extract document class from LaTeX content."""
|
|
438
|
+
match = re.search(r'\\documentclass(?:\[[^]]*\])?\{([^}]+)\}', content)
|
|
439
|
+
return match.group(1) if match else 'unknown'
|
|
440
|
+
|
|
441
|
+
def _extract_packages(self, content: str) -> List[str]:
|
|
442
|
+
"""Extract all packages used in the document."""
|
|
443
|
+
return re.findall(r'\\usepackage(?:\[[^]]*\])?\{([^}]+)\}', content)
|
|
444
|
+
|
|
445
|
+
def _check_section_hierarchy(self, sections: List[Dict], doc_class: str) -> List[LaTeXIssue]:
|
|
446
|
+
"""Check if section hierarchy is logical."""
|
|
447
|
+
issues = []
|
|
448
|
+
|
|
449
|
+
if not sections:
|
|
450
|
+
return issues
|
|
451
|
+
|
|
452
|
+
# Define hierarchy levels
|
|
453
|
+
hierarchy = {
|
|
454
|
+
'article': ['section', 'subsection', 'subsubsection'],
|
|
455
|
+
'report': ['chapter', 'section', 'subsection', 'subsubsection'],
|
|
456
|
+
'book': ['part', 'chapter', 'section', 'subsection', 'subsubsection']
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
expected_hierarchy = hierarchy.get(doc_class, hierarchy['article'])
|
|
460
|
+
|
|
461
|
+
# Check for proper nesting
|
|
462
|
+
prev_level_index = -1
|
|
463
|
+
for section in sections:
|
|
464
|
+
level = section['level']
|
|
465
|
+
if level in expected_hierarchy:
|
|
466
|
+
level_index = expected_hierarchy.index(level)
|
|
467
|
+
|
|
468
|
+
# Check if we're skipping levels
|
|
469
|
+
if level_index > prev_level_index + 1:
|
|
470
|
+
issues.append(LaTeXIssue(
|
|
471
|
+
severity='warning',
|
|
472
|
+
category='structure',
|
|
473
|
+
description=f'Section hierarchy skip detected at "{section["title"]}"',
|
|
474
|
+
line_number=section['line'],
|
|
475
|
+
suggestion=f'Consider adding intermediate section levels'
|
|
476
|
+
))
|
|
477
|
+
|
|
478
|
+
prev_level_index = level_index
|
|
479
|
+
|
|
480
|
+
return issues
|
|
481
|
+
|
|
482
|
+
def _generate_suggestions(self, issues: List[LaTeXIssue], structure_result: Dict, typography_result: Dict) -> List[str]:
|
|
483
|
+
"""Generate overall improvement suggestions."""
|
|
484
|
+
suggestions = []
|
|
485
|
+
|
|
486
|
+
# Priority suggestions based on severity
|
|
487
|
+
error_count = sum(1 for issue in issues if issue.severity == 'error')
|
|
488
|
+
warning_count = sum(1 for issue in issues if issue.severity == 'warning')
|
|
489
|
+
|
|
490
|
+
if error_count > 0:
|
|
491
|
+
suggestions.append(f"Fix {error_count} critical LaTeX errors for compilation")
|
|
492
|
+
|
|
493
|
+
if warning_count > 0:
|
|
494
|
+
suggestions.append(f"Address {warning_count} formatting warnings for better quality")
|
|
495
|
+
|
|
496
|
+
# Specific recommendations
|
|
497
|
+
if structure_result['score'] < 20:
|
|
498
|
+
suggestions.append("Improve document structure and section organization")
|
|
499
|
+
|
|
500
|
+
if typography_result['score'] < 20:
|
|
501
|
+
suggestions.append("Enhance typography with proper packages and spacing")
|
|
502
|
+
|
|
503
|
+
# Package recommendations
|
|
504
|
+
common_packages = ['booktabs', 'microtype', 'hyperref']
|
|
505
|
+
missing_packages = [pkg for pkg in common_packages if not any(pkg in issue.description for issue in issues)]
|
|
506
|
+
|
|
507
|
+
if len(missing_packages) > 1:
|
|
508
|
+
suggestions.append(f"Consider adding packages: {', '.join(missing_packages)}")
|
|
509
|
+
|
|
510
|
+
return suggestions[:5] # Limit to top 5 suggestions
|