Tutorial: PDF to MDX
Hướng dẫn thực hành tạo Agent Skill để chuyển đổi file PDF đề thi học sinh giỏi thành trang MDX hiển thị tốt trên web.
Bài toán
Phần tiêu đề “Bài toán”Tình huống: Bạn có nhiều file PDF đề thi học sinh giỏi với chất lượng khác nhau (scan, text, hỗn hợp). Cần chuyển thành MDX để:
- Hiển thị tốt trên website
- Dễ tìm kiếm và index
- Hỗ trợ code syntax highlighting, bảng, công thức toán
Thách thức:
- PDF chất lượng scan kém cần OCR
- Đề thi chứa code (Python, C++, Pascal…)
- Bảng dữ liệu phức tạp
- Công thức toán học (LaTeX/KaTeX)
- Hình ảnh minh họa
Thiết kế Skill
Phần tiêu đề “Thiết kế Skill”Cấu trúc thư mục
Phần tiêu đề “Cấu trúc thư mục”pdf-to-mdx-converter/├── SKILL.md # File chính├── reference.md # Hướng dẫn chi tiết├── templates/│ └── exam-template.mdx # Template đề thi└── scripts/ ├── extract_text.py # Extract text từ PDF ├── detect_content.py # Nhận diện loại content └── convert_to_mdx.py # Chuyển đổi sang MDXTạo SKILL.md
Phần tiêu đề “Tạo SKILL.md”---name: pdf-to-mdx-converterdescription: Chuyển đổi file PDF đề thi học sinh giỏi thành MDX, xử lý code blocks, bảng, và công thức toán họcdependencies: python>=3.8, pypdf>=3.0, pdfplumber>=0.9, pytesseract>=0.3---
# PDF to MDX Converter
Skill này chuyển đổi file PDF đề thi học sinh giỏi thành trang MDXcó thể hiển thị tốt trên web với:- Code syntax highlighting- Bảng dữ liệu- Công thức toán học (KaTeX)- Hình ảnh
## Quy trình xử lý
1. **Đọc PDF** - Extract text và images từ file2. **Nhận diện content** - Phân loại: text, code, bảng, công thức3. **Chuyển đổi** - Convert từng phần sang MDX format4. **Output** - Tạo file .mdx hoàn chỉnh
## Khi nào sử dụng
- Chuyển đổi đề thi PDF sang web format- Extract content từ tài liệu học thuật- Digitize tài liệu giáo dục
## Xem reference.md để biết chi tiết implementationXử lý các thành phần
Phần tiêu đề “Xử lý các thành phần”1. Extract Text từ PDF
Phần tiêu đề “1. Extract Text từ PDF”import pdfplumberfrom pypdf import PdfReaderimport pytesseractfrom PIL import Imageimport io
def extract_text(pdf_path): """Extract text từ PDF, hỗ trợ cả text-based và scanned PDF"""
results = []
with pdfplumber.open(pdf_path) as pdf: for i, page in enumerate(pdf.pages): # Thử extract text trực tiếp text = page.extract_text()
# Nếu không có text (scanned PDF), dùng OCR if not text or len(text.strip()) < 50: # Convert page to image img = page.to_image(resolution=300) pil_image = img.original
# OCR với pytesseract text = pytesseract.image_to_string( pil_image, lang='vie+eng', # Hỗ trợ tiếng Việt và Anh config='--psm 6' # Assume uniform block of text )
results.append({ 'page': i + 1, 'text': text, 'tables': page.extract_tables(), 'images': extract_images(page) })
return results
def extract_images(page): """Extract images từ page""" images = [] for img in page.images: images.append({ 'x0': img['x0'], 'y0': img['top'], 'width': img['width'], 'height': img['height'] }) return images2. Nhận diện loại Content
Phần tiêu đề “2. Nhận diện loại Content”import re
def detect_content_type(text): """Nhận diện loại content: code, math, table, text"""
content_blocks = [] lines = text.split('\n')
current_block = {'type': 'text', 'content': []}
for line in lines: detected_type = classify_line(line)
if detected_type != current_block['type']: if current_block['content']: content_blocks.append(current_block) current_block = {'type': detected_type, 'content': []}
current_block['content'].append(line)
if current_block['content']: content_blocks.append(current_block)
return content_blocks
def classify_line(line): """Phân loại một dòng text"""
# Code patterns code_patterns = [ r'^\s*(def |class |import |from |if |for |while |return |print\()', # Python r'^\s*(#include|int main|void |printf|scanf)', # C/C++ r'^\s*(program |var |begin |end\.|procedure |function )', # Pascal r'^\s*```', # Markdown code block ]
for pattern in code_patterns: if re.search(pattern, line, re.IGNORECASE): return 'code'
# Math patterns math_patterns = [ r'\$.*\$', # Inline math r'\\\[.*\\\]', # Display math r'\\frac|\\sum|\\int|\\sqrt|\\alpha|\\beta', # LaTeX commands r'[∑∫∏√∞≤≥≠±×÷]', # Math symbols ]
for pattern in math_patterns: if re.search(pattern, line): return 'math'
return 'text'
def detect_programming_language(code_block): """Detect ngôn ngữ lập trình"""
code = '\n'.join(code_block['content'])
if re.search(r'def |import |print\(|class \w+:', code): return 'python' elif re.search(r'#include|int main|printf|scanf|void ', code): return 'cpp' elif re.search(r'program |begin |end\.|writeln|readln', code, re.IGNORECASE): return 'pascal' elif re.search(r'public class|System\.out|void main', code): return 'java'
return 'text'3. Xử lý Code Blocks
Phần tiêu đề “3. Xử lý Code Blocks”def format_code_block(block): """Format code block cho MDX"""
language = detect_programming_language(block) code = '\n'.join(block['content'])
# Clean up code code = code.strip()
# Loại bỏ số dòng nếu có code = re.sub(r'^\d+\s*[:\|]\s*', '', code, flags=re.MULTILINE)
return f"""```{language}{code}“””
### 4. Xử lý Công thức Toán
```pythondef format_math(block): """Convert công thức toán sang KaTeX format cho MDX"""
text = '\n'.join(block['content'])
# Đã có LaTeX format if re.search(r'\$.*\$|\\\[|\\\(', text): return text
# Convert math symbols thông dụng conversions = { '√': r'\sqrt', '∑': r'\sum', '∫': r'\int', '∞': r'\infty', '≤': r'\leq', '≥': r'\geq', '≠': r'\neq', '×': r'\times', '÷': r'\div', '±': r'\pm', 'α': r'\alpha', 'β': r'\beta', 'γ': r'\gamma', 'π': r'\pi', }
for symbol, latex in conversions.items(): text = text.replace(symbol, latex)
# Wrap trong $ nếu chưa có if not text.startswith('$'): # Inline math if len(text) < 50: text = f'${text}$' else: # Display math text = f'$$\n{text}\n$$'
return text5. Xử lý Bảng
Phần tiêu đề “5. Xử lý Bảng”def format_table(table_data): """Convert table data sang Markdown table"""
if not table_data or not table_data[0]: return ""
# Clean data cleaned = [] for row in table_data: cleaned_row = [str(cell).strip() if cell else '' for cell in row] cleaned.append(cleaned_row)
# Xác định số cột max_cols = max(len(row) for row in cleaned)
# Normalize rows for row in cleaned: while len(row) < max_cols: row.append('')
# Build markdown table lines = []
# Header header = '| ' + ' | '.join(cleaned[0]) + ' |' lines.append(header)
# Separator separator = '| ' + ' | '.join(['---'] * max_cols) + ' |' lines.append(separator)
# Data rows for row in cleaned[1:]: line = '| ' + ' | '.join(row) + ' |' lines.append(line)
return '\n'.join(lines)6. Tạo file MDX hoàn chỉnh
Phần tiêu đề “6. Tạo file MDX hoàn chỉnh”def convert_to_mdx(pdf_path, output_path): """Main function: Convert PDF to MDX"""
# Extract content pages = extract_text(pdf_path)
# Build MDX content mdx_content = []
# Frontmatter mdx_content.append('''---title: "Đề thi Học sinh giỏi"description: "Đề thi được chuyển đổi từ PDF"---''')
for page_data in pages: mdx_content.append(f"\n## Trang {page_data['page']}\n")
# Process text content blocks = detect_content_type(page_data['text'])
for block in blocks: if block['type'] == 'code': mdx_content.append(format_code_block(block)) elif block['type'] == 'math': mdx_content.append(format_math(block)) else: mdx_content.append('\n'.join(block['content']))
# Process tables for table in page_data['tables']: mdx_content.append('\n' + format_table(table) + '\n')
# Write output with open(output_path, 'w', encoding='utf-8') as f: f.write('\n'.join(mdx_content))
return output_pathTemplate MDX đầu ra
Phần tiêu đề “Template MDX đầu ra”Dưới đây là ví dụ output MDX mà skill sẽ tạo ra:
---title: "Đề thi HSG Tin học - Tỉnh ABC 2024"description: "Đề thi chọn học sinh giỏi cấp tỉnh môn Tin học"subject: "Tin học"level: "THPT"year: 2024---
# ĐỀ THI HỌC SINH GIỎI CẤP TỈNH
**Môn:** Tin học**Thời gian:** 180 phút**Năm:** 2024
---
## Bài 1: Dãy con tăng (4 điểm)
Cho dãy số nguyên $a_1, a_2, ..., a_n$ với $1 \leq n \leq 10^5$.
**Yêu cầu:** Tìm dãy con tăng dài nhất.
**Input:** File `DAYCON.INP`- Dòng 1: Số nguyên $n$- Dòng 2: $n$ số nguyên $a_i$ ($|a_i| \leq 10^9$)
**Output:** File `DAYCON.OUT`- Dòng 1: Độ dài dãy con tăng dài nhất
**Ví dụ:**
| DAYCON.INP | DAYCON.OUT ||------------|------------|| 6 | 4 || 1 3 2 4 3 5| |
---
## Bài 2: Đường đi ngắn nhất (5 điểm)
Cho đồ thị có hướng $G = (V, E)$ với $n$ đỉnh và $m$ cạnh.
Công thức: $$d(u, v) = \min_{p \in P(u,v)} \sum_{e \in p} w(e)$$
### Code mẫu (minh họa syntax highlighting)
```cpp#include <bits/stdc++.h>using namespace std;
void dijkstra(int s, int n) { // ... implementation}Hết đề
> **Lưu ý:** Đây là ví dụ minh họa cách skill format output. Nội dung thực tế sẽ được trích xuất từ PDF đề thi của bạn.
## Sử dụng Skill
Sau khi tạo và upload skill, bạn có thể yêu cầu Claude:“Chuyển đổi file de-thi-tin-hoc-2024.pdf thành MDX, đảm bảo format đúng code C++, công thức toán, và bảng dữ liệu”
Claude sẽ:1. Load skill `pdf-to-mdx-converter`2. Chạy scripts extract và convert3. Tạo file .mdx hoàn chỉnh4. Trả về file để bạn download
## Tips xử lý PDF chất lượng kém
1. **Tăng resolution OCR** khi PDF scan mờ: ```python img = page.to_image(resolution=600) # Thay vì 300-
Pre-process image trước OCR:
from PIL import ImageFilter, ImageEnhanceimg = img.filter(ImageFilter.SHARPEN)enhancer = ImageEnhance.Contrast(img)img = enhancer.enhance(2.0) -
Manual review cho công thức phức tạp - OCR khó nhận diện LaTeX chính xác
-
Sử dụng GPT-4 Vision cho PDF scan phức tạp (thay vì OCR truyền thống)
Mở rộng
Phần tiêu đề “Mở rộng”Skill này có thể mở rộng để:
- Hỗ trợ thêm ngôn ngữ lập trình
- Auto-generate solutions từ đề thi
- Tạo metadata SEO tự động
- Export sang nhiều format (HTML, LaTeX, Word)
Kết luận
Phần tiêu đề “Kết luận”Với Agent Skill này, bạn có thể tự động hóa việc chuyển đổi hàng loạt PDF đề thi sang MDX, tiết kiệm thời gian đáng kể so với làm thủ công.
Tiếp theo: Thử áp dụng pattern này để tạo skills cho các workflow khác trong công việc của bạn!