#!/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 or --data-inline ''"})) sys.exit(1) result = create_xlsx(data, args.out) print(json.dumps(result, ensure_ascii=False)) if __name__ == "__main__": main()