"""Shared, presentation-independent helpers for document ZIP exports.""" import posixpath from zipfile import ZipFile def safe_archive_part(value: object, fallback: str) -> str: """Sanitize one ZIP path component while preserving Unicode names.""" raw = str(value or fallback) cleaned = "".join( "_" if char in {"/", "\\"} or ord(char) < 32 else char for char in raw ).strip(" .") return cleaned or fallback def unique_archive_path(archive_path: str, used_paths: set[str]) -> str: """Reserve a deterministic, collision-free path inside an archive.""" if archive_path not in used_paths: used_paths.add(archive_path) return archive_path directory = posixpath.dirname(archive_path) filename = posixpath.basename(archive_path) stem, extension = posixpath.splitext(filename) counter = 2 while True: candidate_name = f"{stem} ({counter}){extension}" candidate = ( posixpath.join(directory, candidate_name) if directory else candidate_name ) if candidate not in used_paths: used_paths.add(candidate) return candidate counter += 1 def document_archive_filename(document) -> str: """Return the best available filename for a document asset.""" return safe_archive_part( getattr(document.file, "original_filename", "") or getattr(document.file, "name", "") or document.title or f"document-{document.id}", f"document-{document.id}", ) def write_document_to_archive( archive: ZipFile, *, document, archive_path: str, ) -> None: """Read a filer asset safely and write it to an open ZIP archive.""" file_obj = document.file.file try: file_obj.open("rb") archive.writestr(archive_path, file_obj.read()) finally: file_obj.close()