Setting the file. One moment. Init · PPTX · anthropics/skills · Skills DocsOpc Dig Sig
scripts/office/helpers/__init__.py
Python·111 lines·3 KB
".dotx"
:
"docx"
,
13 ".pptx": "pptx",
14 ".potx": "pptx",
15 ".xlsx": "xlsx",
16 ".xltx": "xlsx",
17}
18
19_SCHEME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9+.\-]*:")
20
21SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
22
23
24def opc_target(target: str, source_part: str, target_mode: str = "") -> str | None:
25 if not target:
26 return None
27 if target_mode.lower() == "external":
28 return None
29 if _SCHEME_RE.match(target):
30 return None
31
32 target = urllib.parse.unquote(target)
33
34 if "\\" in target:
35 raise ValueError(f"relationship target is not a POSIX part name: {target!r}")
36
37 if target.startswith("/"):
38 joined = target.lstrip("/")
39 else:
40 joined = posixpath.join(posixpath.dirname(source_part), target)
41
42 parts: list[str] = []
43 for segment in posixpath.normpath(joined).split("/"):
44 if segment in ("", "."):
45 continue
46 if segment == "..":
47 if not parts:
48 raise ValueError(f"relationship target escapes the package: {target!r}")
49 parts.pop()
50 else:
51 parts.append(segment)
52
53 if not parts:
54 raise ValueError(f"relationship target resolves to nothing: {target!r}")
55 return "/".join(parts)
56
57
58def rels_source_part(rels_file: Path, unpacked_dir: Path) -> str:
59 owner_dir = rels_file.parent.parent.relative_to(unpacked_dir)
60 return posixpath.join(owner_dir.as_posix(), rels_file.name[: -len(".rels")]).lstrip("./")
61
62
63def part_text(data: bytes) -> str:
64 return data.decode("utf-8", "surrogateescape")
65
66
67XML_SPACE = " \t\r\n"
68
69
70def rendered_text(text: str, preserve: bool) -> str:
71 return text if preserve else text.strip(XML_SPACE)
72
73
74def safe_extract(zf: zipfile.ZipFile, dest: Path) -> None:
75 dest = dest.resolve()
76 for m in zf.infolist():
77 if stat.S_ISLNK(m.external_attr >> 16):
78 raise ValueError(f"symlink archive entry not allowed: {m.filename!r}")
79 target = (dest / m.filename).resolve()
80 if not target.is_relative_to(dest):
81 raise ValueError(f"unsafe archive entry: {m.filename!r}")
82 zf.extract(m, dest)
83
84
85def rezip(src_dir: Path, out_path: Path) -> None:
86 files = sorted(p for p in src_dir.rglob("*") if p.is_file())
87 ct = src_dir / "[Content_Types].xml"
88 fd, tmp_name = tempfile.mkstemp(
89 prefix=out_path.name + ".", suffix=".tmp", dir=out_path.parent
90 )
91 tmp_out = Path(tmp_name)
92 try:
93 with os.fdopen(fd, "wb") as fh:
94 with zipfile.ZipFile(fh, "w", zipfile.ZIP_DEFLATED) as zf:
95 if ct.exists():
96 zf.write(ct, ct.relative_to(src_dir), compress_type=zipfile.ZIP_STORED)
97 for f in files:
98 if f == ct:
99 continue
100 zf.write(f, f.relative_to(src_dir))
101 if out_path.exists():
102 mode = out_path.stat().st_mode & 0o777
103 else:
104 umask = os.umask(0)
105 os.umask(umask)
106 mode = 0o666 & ~umask
107 os.chmod(tmp_out, mode)
108 os.replace(tmp_out, out_path)
109 finally:
110 if tmp_out.exists():
111 tmp_out.unlink()