pdf-annotation-merge/app/bluebeam_bci.py

104 lines
3.2 KiB
Python

#!/usr/bin/env python3
from pathlib import Path
import argparse
import csv
BASE_DIR = Path(__file__).resolve().parents[1]
DATA_DIR = BASE_DIR / "data"
OUTPUT_DIR = BASE_DIR / "output"
def clean_value(v):
if v is None:
return ""
return str(v).strip()
def bci_escape(v):
v = clean_value(v)
v = v.replace("\\", "\\\\")
v = v.replace("'", "\\'")
v = v.replace("\n", "\\n")
return v
def read_csv(path):
with open(path, newline="", encoding="utf-8-sig") as f:
return list(csv.DictReader(f))
def dict_to_bci_payload(d):
parts = []
for k, v in d.items():
if clean_value(v) != "":
parts.append(f"'{bci_escape(k)}':'{bci_escape(v)}'")
return "{" + ",".join(parts) + "}"
def main():
parser = argparse.ArgumentParser(description="Generate Bluebeam BCI script from Markups CSV + update CSV.")
parser.add_argument("--bb-csv", default=str(DATA_DIR / "bluebeam_markups.csv"))
parser.add_argument("--updates-csv", default=str(DATA_DIR / "bluebeam_updates.csv"))
parser.add_argument("--out", default=str(OUTPUT_DIR / "update_columns.bci"))
parser.add_argument("--pdf-path", default=r"C:\PATH\TO\TARGET.pdf")
parser.add_argument("--match-column", default="Comment")
parser.add_argument("--page-column", default="Page Index")
parser.add_argument("--id-column", default="ID")
parser.add_argument("--contains", action="store_true")
args = parser.parse_args()
OUTPUT_DIR.mkdir(exist_ok=True)
bb_rows = read_csv(args.bb_csv)
update_rows = read_csv(args.updates_csv)
if not bb_rows:
raise ValueError("Bluebeam CSV is empty.")
if not update_rows:
raise ValueError("Updates CSV is empty.")
required_bb = {args.page_column, args.id_column, args.match_column}
missing_bb = required_bb - set(bb_rows[0].keys())
if missing_bb:
raise ValueError(f"Bluebeam CSV missing required columns: {sorted(missing_bb)}")
if "match_value" not in update_rows[0]:
raise ValueError("Updates CSV missing required column: match_value")
lines = [f"Open('{args.pdf_path}', '')"]
total_matches = 0
for urow in update_rows:
match_value = clean_value(urow.get("match_value"))
if not match_value:
continue
col_data = {k: v for k, v in urow.items() if k != "match_value" and clean_value(v) != ""}
if not col_data:
continue
payload = dict_to_bci_payload(col_data)
for brow in bb_rows:
target = clean_value(brow.get(args.match_column))
matched = match_value in target if args.contains else target == match_value
if not matched:
continue
page_index = clean_value(brow.get(args.page_column))
markup_id = clean_value(brow.get(args.id_column))
if not page_index or not markup_id:
continue
lines.append(f'ColumnDataSet({page_index},"{markup_id}","{payload}")')
total_matches += 1
lines += ["Save()", "Close()"]
out_path = Path(args.out)
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"WROTE: {out_path}")
print(f"MATCHED MARKUPS: {total_matches}")
if __name__ == "__main__":
main()