144 lines
4.7 KiB
Python
Raw Normal View History

2026-05-20 21:39:12 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
xlsx_create.py Create xlsx from JSON data using openpyxl.
Usage:
python3 xlsx_create.py --data data.json --out output.xlsx
python3 xlsx_create.py --data-inline '{"headers":["A","B"],"rows":[["1","2"]]}' --out output.xlsx
JSON format:
{
"title": "Sheet title (optional, used as sheet name)",
"headers": ["Col1", "Col2", ...],
"rows": [["val1", "val2", ...], ...],
"col_widths": [15, 20, ...] // optional
}
Exit codes: 0 success, 1 bad args, 2 missing dep
"""
import argparse
import json
import os
import sys
try:
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
except ImportError:
print(json.dumps({"status": "error", "error": "openpyxl not installed", "hint": "pip install openpyxl"}))
sys.exit(2)
def create_xlsx(data: dict, out_path: str) -> dict:
wb = Workbook()
ws = wb.active
ws.title = data.get("title", "Sheet1")[:31]
headers = data.get("headers", [])
rows = data.get("rows", [])
col_widths = data.get("col_widths", [])
# Header row styling
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_align = Alignment(horizontal="center", vertical="center")
thin_border = Border(
left=Side(style="thin"),
right=Side(style="thin"),
top=Side(style="thin"),
bottom=Side(style="thin"),
)
# Write headers
for col_idx, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_align
cell.border = thin_border
# Write data rows
for row_idx, row_data in enumerate(rows, 2):
for col_idx, value in enumerate(row_data, 1):
cell = ws.cell(row=row_idx, column=col_idx, value=_auto_type(value))
cell.border = thin_border
cell.alignment = Alignment(vertical="center")
# Column widths
for col_idx, width in enumerate(col_widths, 1):
ws.column_dimensions[chr(64 + col_idx) if col_idx <= 26 else f"{chr(64 + (col_idx-1)//26)}{chr(65 + (col_idx-1)%26)}"].width = width
# Auto-width for columns without explicit width
if not col_widths:
for col_idx in range(1, len(headers) + 1):
max_len = len(str(headers[col_idx - 1])) if col_idx <= len(headers) else 8
for row_idx in range(2, min(len(rows) + 2, 52)):
if col_idx <= len(rows[row_idx - 2]):
cell_len = len(str(rows[row_idx - 2][col_idx - 1]))
max_len = max(max_len, cell_len)
col_letter = chr(64 + col_idx) if col_idx <= 26 else f"{chr(64 + (col_idx-1)//26)}{chr(65 + (col_idx-1)%26)}"
ws.column_dimensions[col_letter].width = min(max_len + 4, 50)
# Freeze header row
ws.freeze_panes = "A2"
# Auto-filter
if headers:
last_col = chr(64 + len(headers)) if len(headers) <= 26 else f"{chr(64 + (len(headers)-1)//26)}{chr(65 + (len(headers)-1)%26)}"
ws.auto_filter.ref = f"A1:{last_col}{len(rows) + 1}"
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
wb.save(out_path)
return {
"status": "ok",
"out": out_path,
"rows": len(rows),
"columns": len(headers),
"size_kb": os.path.getsize(out_path) // 1024,
}
def _auto_type(value):
if value is None:
return ""
if isinstance(value, (int, float)):
return value
s = str(value)
try:
return int(s)
except ValueError:
pass
try:
return float(s)
except ValueError:
pass
return s
def main():
parser = argparse.ArgumentParser(description="Create xlsx from JSON data")
parser.add_argument("--data", help="Path to JSON data file")
parser.add_argument("--data-inline", help="Inline JSON data string")
parser.add_argument("--out", required=True, help="Output xlsx file path")
args = parser.parse_args()
if args.data and os.path.exists(args.data):
with open(args.data, encoding="utf-8") as f:
data = json.load(f)
elif args.data_inline:
data = json.loads(args.data_inline)
elif args.data and (args.data.strip().startswith("{") or args.data.strip().startswith("[")):
data = json.loads(args.data)
else:
print(json.dumps({"status": "error", "error": "No data provided. Use --data <file> or --data-inline '<json>'"}))
sys.exit(1)
result = create_xlsx(data, args.out)
print(json.dumps(result, ensure_ascii=False))
if __name__ == "__main__":
main()