Files
vincent afb10aa52e Add FastMCP tool for web scrapping.
Add logging info for tools calling
2026-07-29 16:28:15 +09:00

274 lines
8.5 KiB
Python

# html_template = """
# <!DOCTYPE html>
# <html>
# <head>
# <meta charset="utf-8">
# <style>
# /* Existing styles... */
# /* Styles for Links */
# a {{
# color: #0000ff; /* Sets the link color to blue */
# text-decoration: underline; /* Ensures the underline is visible */
# }}
# a:visited {{
# color: #0000ff; /* Keeps the link blue even after it is clicked */
# }}
# /* Styles for clean Table Lines */
# table {{
# width: 100%;
# border-collapse: collapse; /* Merges adjacent cell borders into a single line */
# margin: 20px 0;
# font-size: 11pt;
# }}
# th, td {{
# border: 1px solid #bdc3c7; /* Draws the actual grid lines */
# padding: 10px;
# text-align: left;
# }}
# th {{
# background-color: #f2f4f4; /* Optional: adds a neat background header color */
# font-weight: bold;
# color: #2c3e50;
# }}
# tr:nth-child(even) {{
# background-color: #f9f9f9; /* Optional: adds zebra striping to rows */
# }}
# /* Table of Contents Styles */
# .toc-list {{
# list-style-type: decimal;
# padding-left: 20px;
# margin: 10px 0;
# }}
# .toc-list li {{
# margin-bottom: 6px;
# font-size: 11pt;
# }}
# /* Styles the nested sub-sections */
# .toc-list ul {{
# list-style-type: none; /* Removes numbers/bullets from sub-items */
# padding-left: 20px; /* Creates the indent */
# margin: 4px 0;
# }}
# .toc-list ul li {{
# position: relative;
# font-size: 10.5pt;
# color: #555;
# }}
# /* Adds a clean dash prefix (-) to the sub-items */
# .toc-list ul li::before {{
# content: "- ";
# position: absolute;
# left: -12px;
# }}
# </style>
# </head>
# <body>
# {content}
# </body>
# </html>
# """
html_template = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
/* --- WeasyPrint Page Parameters --- */
@page {{
size: A4;
margin: 20mm;
@bottom-right {{
content: counter(page) " / " counter(pages);
font-family: Arial, sans-serif;
font-size: 9pt;
color: #7f8c8d;
}}
}}
body {{
font-family: Arial, sans-serif;
color: #2c3e50;
line-height: 1.6;
font-size: 11pt;
}}
/* --- Table of Contents Component --- */
.toc-box {{
margin: 25px 0;
padding: 20px;
background-color: #fcfcfc;
border: 1px solid #eaeaea;
border-radius: 4px;
}}
/* Direct Parents (1., 2., 3.) */
.toc-box > ol {{
list-style-type: decimal;
padding-left: 20px;
margin: 0;
}}
.toc-box > ol > li {{
font-size: 12pt;
font-weight: bold;
margin-top: 12px;
color: #2c3e50;
}}
/* Targets sub-items that Python and Markdown nested inside the <li> tag */
.toc-box ol li ul {{
list-style-type: none; /* Destroys standard browser bullet points */
padding-left: 20px; /* Indents sub-items perfectly to the right */
margin: 6px 0 0 0;
}}
.toc-box ol li ul li {{
font-size: 11pt;
font-weight: normal; /* Removes parent bold styles from sub-items */
color: #555555;
margin-bottom: 5px;
position: relative;
}}
/* Injects a clean layout hyphen prefix natively via CSS */
.toc-box ol li ul li::before {{
content: "- ";
font-weight: bold;
color: #7f8c8d;
}}
/* --- Global Structural Typography --- */
h1, h2, h3, h4, h5, h6 {{
color: #2c3e50;
font-weight: bold;
page-break-after: avoid;
break-after: avoid;
}}
table {{
width: 100%;
border-collapse: collapse;
margin: 24px 0;
font-size: 10.5pt;
page-break-inside: auto;
break-inside: auto;
}}
tr {{ page-break-inside: avoid; break-inside: avoid; }}
th, td {{ border: 1px solid #bdc3c7; padding: 10px; text-align: left; }}
th {{ background-color: #f2f4f4; font-weight: bold; }}
a {{ color: #2980b9; text-decoration: none; }}
.page-break {{ page-break-before: always; }}
</style>
</head>
<body>
{content}
</body>
</html>
"""
import re
import markdown
from html.parser import HTMLParser
from weasyprint import HTML
class TOCHtmlRestructurer(HTMLParser):
def __init__(self):
super().__init__()
self.output = []
self.level_stack = [] # Tracks nesting hierarchy
self.parent_count = 0
self.child_count = 0
self.capture_text = False
self.current_item_text = ""
self.current_href = None # Tracks anchor references dynamically
def handle_starttag(self, tag, attrs):
if tag in ['ol', 'ul']:
if not self.level_stack:
self.level_stack.append('parent')
else:
self.level_stack.append('child')
elif tag == 'li':
self.capture_text = True
self.current_item_text = ""
self.current_href = None
elif tag == 'a' and self.capture_text:
# Capture the link anchor destination safely
attrs_dict = dict(attrs)
self.current_href = attrs_dict.get('href')
def handle_endtag(self, tag):
if tag in ['ol', 'ul']:
if self.level_stack:
self.level_stack.pop()
elif tag == 'li':
self.capture_text = False
self.process_accumulated_item()
def handle_data(self, data):
if self.capture_text:
self.current_item_text += data
def process_accumulated_item(self):
# 1. Clean out stray prefix digits and list flags safely
clean = self.current_item_text.strip()
clean = re.sub(r'^([\d\.\s\-•]*\d+\.\d+|[\d\.\s\-•]*\d+\.)\s*', '', clean)
clean = re.sub(r'^\d+\s+(?=[A-Za-z])', '', clean)
clean = re.sub(r'^[\s\-•]*', '', clean).strip()
if not clean:
return
# 2. Check current depth level stack state
current_state = self.level_stack[-1] if self.level_stack else 'parent'
# 3. Rebuild the text as a clickable link if an anchor was present
if self.current_href:
display_text = f'<a href="{self.current_href}">{clean}</a>'
else:
display_text = clean
# 4. Generate structured HTML outputs with calculated taxonomy counters
if current_state == 'parent':
self.parent_count += 1
self.child_count = 0
self.output.append(f'<div class="toc-parent">{self.parent_count}. {display_text}</div>')
else:
self.child_count += 1
self.output.append(f'<div class="toc-child">{self.parent_count}.{self.child_count} {display_text}</div>')
def extract_and_fix_toc_blocks(html_content):
"""
Finds the compiled HTML lists block, intercepts it, clears
numbering artifacts structurally via HTML parsing tree mechanics.
"""
match = re.search(r'(<(?:ol|ul)>[\s\S]*?</(?:ol|ul)>)', html_content)
if not match:
return html_content
raw_toc_html = match.group(1)
parser = TOCHtmlRestructurer()
parser.feed(raw_toc_html)
new_toc_html = '<div class="toc-box">\n<h2>Table of Contents</h2>\n'
new_toc_html += '\n'.join(parser.output)
new_toc_html += '\n</div>'
return html_content.replace(raw_toc_html, new_toc_html, 1)
def export_to_pdf(inputText, filename):
"""
Processes plain dynamic markdown, captures structural compilation output,
restructures TOC nodes downstream safely, and outputs a high-fidelity PDF.
"""
html_content = markdown.markdown(inputText, extensions=['tables', 'fenced_code'])
final_body_content = extract_and_fix_toc_blocks(html_content)
final_html = html_template.format(content=final_body_content)
HTML(string=final_html).write_pdf(filename)