feat: add Chinese office productivity plugins
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Original XLSX helper for inspection, error scans, and CSV conversion."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from openpyxl.styles import Font, PatternFill
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
|
||||
ERROR_TOKENS = ("#REF!", "#DIV/0!", "#VALUE!", "#NAME?", "#N/A", "#NUM!", "#NULL!")
|
||||
|
||||
|
||||
def inspect(path: Path) -> None:
|
||||
workbook = load_workbook(path, data_only=False, read_only=False)
|
||||
sheets = []
|
||||
for sheet in workbook.worksheets:
|
||||
formula_count = 0
|
||||
nonempty = 0
|
||||
for row in sheet.iter_rows():
|
||||
for cell in row:
|
||||
if cell.value is not None:
|
||||
nonempty += 1
|
||||
if isinstance(cell.value, str) and cell.value.startswith("="):
|
||||
formula_count += 1
|
||||
sheets.append(
|
||||
{
|
||||
"name": sheet.title,
|
||||
"state": sheet.sheet_state,
|
||||
"max_row": sheet.max_row,
|
||||
"max_column": sheet.max_column,
|
||||
"nonempty_cells": nonempty,
|
||||
"formulas": formula_count,
|
||||
"merged_ranges": len(sheet.merged_cells.ranges),
|
||||
"charts": len(sheet._charts),
|
||||
"freeze_panes": str(sheet.freeze_panes) if sheet.freeze_panes else None,
|
||||
}
|
||||
)
|
||||
print(json.dumps({"file": str(path.resolve()), "sheets": sheets}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
def formula_scan(path: Path) -> None:
|
||||
formula_book = load_workbook(path, data_only=False, read_only=False)
|
||||
value_book = load_workbook(path, data_only=True, read_only=False)
|
||||
findings = []
|
||||
for formula_sheet in formula_book.worksheets:
|
||||
value_sheet = value_book[formula_sheet.title]
|
||||
for row in formula_sheet.iter_rows():
|
||||
for cell in row:
|
||||
formula = cell.value
|
||||
cached = value_sheet[cell.coordinate].value
|
||||
if isinstance(formula, str) and formula.startswith("="):
|
||||
formula_upper = formula.upper()
|
||||
if any(token in formula_upper for token in ERROR_TOKENS):
|
||||
findings.append({"sheet": formula_sheet.title, "cell": cell.coordinate, "kind": "formula_text", "value": formula})
|
||||
if isinstance(cached, str) and cached.upper() in ERROR_TOKENS:
|
||||
findings.append({"sheet": formula_sheet.title, "cell": cell.coordinate, "kind": "cached_error", "value": cached})
|
||||
print(json.dumps({"file": str(path.resolve()), "errors": findings, "count": len(findings)}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
def csv_to_xlsx(source: Path, output: Path, delimiter: str | None) -> None:
|
||||
chosen = delimiter
|
||||
if chosen is None:
|
||||
chosen = "\t" if source.suffix.lower() == ".tsv" else ","
|
||||
workbook = Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "Data"
|
||||
with source.open("r", encoding="utf-8-sig", newline="") as handle:
|
||||
reader = csv.reader(handle, delimiter=chosen)
|
||||
for row_index, row in enumerate(reader, start=1):
|
||||
for column_index, value in enumerate(row, start=1):
|
||||
sheet.cell(row=row_index, column=column_index, value=value)
|
||||
if sheet.max_row >= 1:
|
||||
for cell in sheet[1]:
|
||||
cell.font = Font(bold=True, color="FFFFFF")
|
||||
cell.fill = PatternFill("solid", fgColor="1F4E78")
|
||||
sheet.freeze_panes = "A2"
|
||||
sheet.auto_filter.ref = sheet.dimensions
|
||||
for column in range(1, sheet.max_column + 1):
|
||||
width = max((len(str(sheet.cell(row=row, column=column).value or "")) for row in range(1, min(sheet.max_row, 200) + 1)), default=8)
|
||||
sheet.column_dimensions[get_column_letter(column)].width = min(max(width + 2, 10), 45)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
workbook.save(output)
|
||||
inspect(output)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
inspect_cmd = sub.add_parser("inspect")
|
||||
inspect_cmd.add_argument("input", type=Path)
|
||||
scan_cmd = sub.add_parser("formula-scan")
|
||||
scan_cmd.add_argument("input", type=Path)
|
||||
convert_cmd = sub.add_parser("csv-to-xlsx")
|
||||
convert_cmd.add_argument("input", type=Path)
|
||||
convert_cmd.add_argument("--output", required=True, type=Path)
|
||||
convert_cmd.add_argument("--delimiter")
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = build_parser().parse_args()
|
||||
if args.command == "inspect":
|
||||
inspect(args.input)
|
||||
elif args.command == "formula-scan":
|
||||
formula_scan(args.input)
|
||||
elif args.command == "csv-to-xlsx":
|
||||
csv_to_xlsx(args.input, args.output, args.delimiter)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user