laporan-generator 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/laporan-generator/SKILL.md +132 -0
- package/.agents/skills/laporan-generator/agents/openai.yaml +3 -0
- package/.agents/skills/laporan-generator/references/campus-guide.md +120 -0
- package/.agents/skills/laporan-generator/references/metadata-schema.md +76 -0
- package/.agents/skills/laporan-generator/references/preset-schema.md +79 -0
- package/.agents/skills/laporan-generator/references/syntax-cheatsheet.md +122 -0
- package/.agents/skills/laporan-generator/references/template-guide.md +100 -0
- package/.agents/skills/laporan-generator/references/troubleshooting.md +105 -0
- package/.agents/skills/laporan-generator/resources/Makefile +90 -0
- package/.agents/skills/laporan-generator/resources/apa.csl +2273 -0
- package/.agents/skills/laporan-generator/resources/build-docx.sh +122 -0
- package/.agents/skills/laporan-generator/resources/build.sh +102 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab1-pendahuluan.md +60 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab2-tinjauan-pustaka.md +83 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab3-metodologi.md +116 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab4-hasil-dan-pembahasan.md +93 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab5-penutup.md +27 -0
- package/.agents/skills/laporan-generator/resources/cover.md +50 -0
- package/.agents/skills/laporan-generator/resources/docx.lua +416 -0
- package/.agents/skills/laporan-generator/resources/laporan +464 -0
- package/.agents/skills/laporan-generator/resources/laporan.ps1 +552 -0
- package/.agents/skills/laporan-generator/resources/logo.jpg +0 -0
- package/.agents/skills/laporan-generator/resources/metadata.yml +35 -0
- package/.agents/skills/laporan-generator/resources/presets/itb-ta.yml +27 -0
- package/.agents/skills/laporan-generator/resources/presets/its-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/skripsi-4433.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/standard.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/ugm-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/ui-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/unpad-skripsi.yml +27 -0
- package/.agents/skills/laporan-generator/resources/reference.docx +0 -0
- package/.agents/skills/laporan-generator/resources/references.bib +115 -0
- package/.agents/skills/laporan-generator/resources/scripts/bundle.py +75 -0
- package/.agents/skills/laporan-generator/resources/scripts/docx-pagenum.py +192 -0
- package/.agents/skills/laporan-generator/resources/scripts/finalize-docx.py +291 -0
- package/.agents/skills/laporan-generator/resources/scripts/make-reference-docx.py +670 -0
- package/.agents/skills/laporan-generator/resources/scripts/report-doctor.py +211 -0
- package/.agents/skills/laporan-generator/resources/scripts/report-stats.py +149 -0
- package/.agents/skills/laporan-generator/resources/scripts/scan-preset.py +311 -0
- package/.agents/skills/laporan-generator/resources/scripts/setup-deps.ps1 +152 -0
- package/.agents/skills/laporan-generator/resources/scripts/setup-deps.sh +113 -0
- package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +128 -0
- package/.agents/skills/laporan-generator/resources/scripts/validate-preset.py +166 -0
- package/.agents/skills/laporan-generator/resources/template.typ +394 -0
- package/.agents/skills/laporan-generator/scripts/build.sh +78 -0
- package/.agents/skills/laporan-generator/scripts/bundle.py +75 -0
- package/.agents/skills/laporan-generator/scripts/docx-pagenum.py +192 -0
- package/.agents/skills/laporan-generator/scripts/finalize-docx.py +152 -0
- package/.agents/skills/laporan-generator/scripts/laporan +447 -0
- package/.agents/skills/laporan-generator/scripts/laporan.ps1 +529 -0
- package/.agents/skills/laporan-generator/scripts/make-reference-docx.py +630 -0
- package/.agents/skills/laporan-generator/scripts/report-doctor.py +211 -0
- package/.agents/skills/laporan-generator/scripts/report-stats.py +149 -0
- package/.agents/skills/laporan-generator/scripts/scan-preset.py +311 -0
- package/.agents/skills/laporan-generator/scripts/setup-deps.ps1 +152 -0
- package/.agents/skills/laporan-generator/scripts/setup-deps.sh +113 -0
- package/.agents/skills/laporan-generator/scripts/test_scripts.py +103 -0
- package/.agents/skills/laporan-generator/scripts/validate-preset.py +166 -0
- package/.agents/workflows/laporan-generator.md +8 -0
- package/LICENSE +21 -0
- package/Makefile +90 -0
- package/README.md +389 -0
- package/apa.csl +2273 -0
- package/bin/laporan-generator.js +71 -0
- package/build-docx.sh +122 -0
- package/build.sh +102 -0
- package/chapters/bab1-pendahuluan.md +60 -0
- package/chapters/bab2-tinjauan-pustaka.md +83 -0
- package/chapters/bab3-metodologi.md +116 -0
- package/chapters/bab4-hasil-dan-pembahasan.md +93 -0
- package/chapters/bab5-penutup.md +27 -0
- package/cover.md +50 -0
- package/docs/campus-guide.md +120 -0
- package/docs/metadata-schema.md +76 -0
- package/docs/preset-schema.md +79 -0
- package/docs/syntax-cheatsheet.md +122 -0
- package/docs/template-guide.md +100 -0
- package/docs/troubleshooting.md +105 -0
- package/docx.lua +416 -0
- package/laporan +464 -0
- package/laporan.ps1 +552 -0
- package/lib/init.js +118 -0
- package/lib/setup.js +33 -0
- package/lib/sync-hosts.js +194 -0
- package/lib/uninstall.js +123 -0
- package/logo.jpg +0 -0
- package/metadata.yml +35 -0
- package/package.json +65 -0
- package/presets/itb-ta.yml +27 -0
- package/presets/its-skripsi.yml +26 -0
- package/presets/skripsi-4433.yml +26 -0
- package/presets/standard.yml +26 -0
- package/presets/ugm-skripsi.yml +26 -0
- package/presets/ui-skripsi.yml +26 -0
- package/presets/unpad-skripsi.yml +27 -0
- package/reference.docx +0 -0
- package/references.bib +115 -0
- package/scripts/bundle.py +75 -0
- package/scripts/docx-pagenum.py +192 -0
- package/scripts/finalize-docx.py +291 -0
- package/scripts/make-reference-docx.py +670 -0
- package/scripts/report-doctor.py +211 -0
- package/scripts/report-stats.py +149 -0
- package/scripts/scan-preset.py +311 -0
- package/scripts/setup-deps.ps1 +152 -0
- package/scripts/setup-deps.sh +113 -0
- package/scripts/test_scripts.py +128 -0
- package/scripts/validate-preset.py +166 -0
- package/template.typ +394 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Isi nilai halaman asli ke field PAGEREF di daftar isi DOCX.
|
|
3
|
+
|
|
4
|
+
Dua-pass:
|
|
5
|
+
1. Render DOCX (yang sudah di-finalize section-nya) via LibreOffice
|
|
6
|
+
(soffice --headless) menjadi PDF.
|
|
7
|
+
2. Baca nomor halaman setiap heading dari teks PDF (footer per halaman),
|
|
8
|
+
lalu tulis sebagai nilai cache PAGEREF di daftar isi.
|
|
9
|
+
|
|
10
|
+
Hasil: daftar isi menampilkan nomor halaman asli di aplikasi manapun
|
|
11
|
+
(Word, LibreOffice, Google Docs) tanpa perlu update field manual.
|
|
12
|
+
|
|
13
|
+
Kalau soffice/pdftotext tidak tersedia, output = input (Word tetap
|
|
14
|
+
memperbarui field saat dibuka karena updateFields=true).
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
python3 scripts/docx-pagenum.py input.docx output.docx
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
26
|
+
import zipfile
|
|
27
|
+
|
|
28
|
+
ROMAN = re.compile(r"^[ivxlcdm]+$", re.I)
|
|
29
|
+
DECIMAL = re.compile(r"^\d+$")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def page_count(pdf):
|
|
33
|
+
out = subprocess.check_output(["pdfinfo", pdf], text=True, timeout=15)
|
|
34
|
+
m = re.search(r"^Pages:\s+(\d+)", out, re.M)
|
|
35
|
+
return int(m.group(1)) if m else 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def page_texts(pdf):
|
|
39
|
+
n = page_count(pdf)
|
|
40
|
+
pages = {}
|
|
41
|
+
for p in range(1, n + 1):
|
|
42
|
+
out = subprocess.check_output(
|
|
43
|
+
["pdftotext", "-f", str(p), "-l", str(p), "-layout", pdf, "-"],
|
|
44
|
+
text=True,
|
|
45
|
+
timeout=15,
|
|
46
|
+
)
|
|
47
|
+
lines = [l.strip() for l in out.splitlines() if l.strip()]
|
|
48
|
+
pages[p] = lines
|
|
49
|
+
return pages
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def footer_of(lines):
|
|
53
|
+
if not lines:
|
|
54
|
+
return ""
|
|
55
|
+
return lines[-1]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def find_toc_start(pages):
|
|
59
|
+
for p, lines in sorted(pages.items()):
|
|
60
|
+
for l in lines:
|
|
61
|
+
if l == "DAFTAR ISI":
|
|
62
|
+
return p
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def first_decimal_page(pages, start):
|
|
67
|
+
for p, lines in sorted(pages.items()):
|
|
68
|
+
if p <= start:
|
|
69
|
+
continue
|
|
70
|
+
if DECIMAL.match(footer_of(lines)):
|
|
71
|
+
return p
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def find_page(pages, needle, start, end):
|
|
76
|
+
for p in range(start, end + 1):
|
|
77
|
+
text = "\n".join(pages.get(p, []))
|
|
78
|
+
if needle in text:
|
|
79
|
+
return p
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def parse_entries(doc):
|
|
84
|
+
entries = []
|
|
85
|
+
par_pat = re.compile(r"<w:p>(?:(?!</w:p>).)*?</w:p>", re.S)
|
|
86
|
+
for pm in par_pat.finditer(doc):
|
|
87
|
+
p = pm.group(0)
|
|
88
|
+
m = re.search(
|
|
89
|
+
r'<w:hyperlink w:anchor="([^"]+)"[^>]*>.*?<w:t[^>]*>([^<]*)</w:t>', p, re.S
|
|
90
|
+
)
|
|
91
|
+
r = re.search(
|
|
92
|
+
r"<w:instrText[^>]*>\s*PAGEREF\s+(\S+)\s+\\h\s*</w:instrText>", p
|
|
93
|
+
)
|
|
94
|
+
if m and r and m.group(1) == r.group(1):
|
|
95
|
+
entries.append((p, m.group(1), m.group(2)))
|
|
96
|
+
return entries
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def needle_of(entry_text):
|
|
100
|
+
if entry_text.startswith("BAB "):
|
|
101
|
+
parts = entry_text.split(" ", 2)
|
|
102
|
+
return parts[2] if len(parts) == 3 else parts[1]
|
|
103
|
+
return entry_text
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render_pdf(docx):
|
|
107
|
+
tmp = tempfile.mkdtemp(prefix="lo_pagenum_")
|
|
108
|
+
profile = os.path.join(tmp, "profile")
|
|
109
|
+
subprocess.run(
|
|
110
|
+
[
|
|
111
|
+
"soffice", "--headless", "--norestore",
|
|
112
|
+
"-env:UserInstallation=file://" + profile,
|
|
113
|
+
"--convert-to", "pdf", "--outdir", tmp, docx,
|
|
114
|
+
],
|
|
115
|
+
check=True,
|
|
116
|
+
capture_output=True,
|
|
117
|
+
timeout=45,
|
|
118
|
+
)
|
|
119
|
+
base = os.path.basename(docx).rsplit(".", 1)[0] + ".pdf"
|
|
120
|
+
return os.path.join(tmp, base)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main():
|
|
124
|
+
src, dst = sys.argv[1], sys.argv[2]
|
|
125
|
+
shutil.copy(src, dst)
|
|
126
|
+
|
|
127
|
+
if not shutil.which("soffice") or not shutil.which("pdftotext"):
|
|
128
|
+
print("[warn] docx-pagenum: soffice/pdftotext tidak ada, nomor halaman "
|
|
129
|
+
"daftar isi memakai nilai cache (Word tetap update saat dibuka)")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
with zipfile.ZipFile(src, "r") as zin:
|
|
133
|
+
names = zin.namelist()
|
|
134
|
+
items = {n: zin.read(n) for n in names}
|
|
135
|
+
doc = items["word/document.xml"].decode("utf-8")
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
pdf = render_pdf(src)
|
|
139
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as err:
|
|
140
|
+
print(f"[warn] docx-pagenum: render LibreOffice gagal/timeout ({err}), nomor halaman memakai nilai cache default.")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
pages = page_texts(pdf)
|
|
145
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as err:
|
|
146
|
+
print(f"[warn] docx-pagenum: ekstraksi teks PDF gagal/timeout ({err}), nomor halaman memakai nilai cache default.")
|
|
147
|
+
return
|
|
148
|
+
finally:
|
|
149
|
+
shutil.rmtree(os.path.dirname(pdf), ignore_errors=True)
|
|
150
|
+
|
|
151
|
+
toc_start = find_toc_start(pages)
|
|
152
|
+
if not toc_start:
|
|
153
|
+
print("[warn] docx-pagenum: halaman DAFTAR ISI tidak ditemukan di render")
|
|
154
|
+
return
|
|
155
|
+
first_dec = first_decimal_page(pages, toc_start)
|
|
156
|
+
if not first_dec:
|
|
157
|
+
print("[warn] docx-pagenum: halaman body (decimal) tidak ditemukan")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
entries = parse_entries(doc)
|
|
161
|
+
injected = 0
|
|
162
|
+
for para, pid, text in entries:
|
|
163
|
+
if pid == "kata-pengantar":
|
|
164
|
+
page = find_page(pages, "KATA PENGANTAR", 1, toc_start - 1)
|
|
165
|
+
else:
|
|
166
|
+
page = find_page(pages, needle_of(text), first_dec, max(pages))
|
|
167
|
+
if not page:
|
|
168
|
+
print(f"[warn] docx-pagenum: heading '{text}' tidak ditemukan di render")
|
|
169
|
+
continue
|
|
170
|
+
value = footer_of(pages[page])
|
|
171
|
+
if not (ROMAN.match(value) or DECIMAL.match(value)):
|
|
172
|
+
print(f"[warn] docx-pagenum: footer halaman {page} tidak valid: {value!r}")
|
|
173
|
+
continue
|
|
174
|
+
new_para = re.sub(
|
|
175
|
+
r'(<w:fldChar w:fldCharType="separate"/></w:r><w:r><w:t[^>]*>)[^<]*(</w:t>)',
|
|
176
|
+
r"\g<1>" + value + r"\g<2>",
|
|
177
|
+
para,
|
|
178
|
+
count=1,
|
|
179
|
+
)
|
|
180
|
+
doc = doc.replace(para, new_para)
|
|
181
|
+
injected += 1
|
|
182
|
+
print(f"[OK] {text} -> halaman {page} (nomor {value})")
|
|
183
|
+
|
|
184
|
+
items["word/document.xml"] = doc.encode("utf-8")
|
|
185
|
+
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
|
|
186
|
+
for n, content in items.items():
|
|
187
|
+
zout.writestr(n, content)
|
|
188
|
+
print(f"[OK] docx-pagenum: {injected} nomor halaman diisi -> {dst}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Finalisasi document.xml hasil pandoc agar penomoran halaman DOCX
|
|
3
|
+
mengikuti PDF:
|
|
4
|
+
|
|
5
|
+
- Cover : tanpa nomor halaman (footerReference dihapus)
|
|
6
|
+
- KATA PENGANTAR + ISI : i, ii (lowerRoman, start 1)
|
|
7
|
+
- BAB I : 1, 2, 3 ... (decimal, start 1)
|
|
8
|
+
- BAB II dst + PUSTAKA : lanjut (tanpa restart)
|
|
9
|
+
|
|
10
|
+
Pandoc otomatis menyisipkan paragraf `w:sectPr` (berisi footerReference
|
|
11
|
+
dari reference.docx) sebelum setiap Heading1. Script ini mengklasifikasikan
|
|
12
|
+
setiap sectPr berdasarkan heading berikutnya dan menyesuaikan isinya.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python3 scripts/finalize-docx.py input.docx output.docx
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import zipfile
|
|
21
|
+
|
|
22
|
+
if sys.platform == "win32":
|
|
23
|
+
try:
|
|
24
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
25
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
SECT_IN_PPR = re.compile(r"<w:pPr><w:sectPr>(.*?)</w:sectPr></w:pPr>", re.S)
|
|
30
|
+
FOOTER_REF = re.compile(r"<w:footerReference[^/]*/>")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def next_heading_text(doc, start):
|
|
34
|
+
nxt = doc[start:]
|
|
35
|
+
m = re.search(r'<w:pStyle w:val="Heading1"[^>]*/>', nxt)
|
|
36
|
+
if not m:
|
|
37
|
+
return ""
|
|
38
|
+
hdr = nxt[m.end():]
|
|
39
|
+
t = re.search(r"<w:t[^>]*>([^<]*)", hdr)
|
|
40
|
+
return t.group(1) if t else ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_front_matter_heading(t):
|
|
44
|
+
norm = t.strip().upper()
|
|
45
|
+
return norm in (
|
|
46
|
+
"KATA PENGANTAR",
|
|
47
|
+
"PRAKATA",
|
|
48
|
+
"ABSTRAK",
|
|
49
|
+
"ABSTRACT",
|
|
50
|
+
"DAFTAR ISI",
|
|
51
|
+
"DAFTAR TABEL",
|
|
52
|
+
"DAFTAR GAMBAR",
|
|
53
|
+
"DAFTAR LAMPIRAN",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_chapter_1(t):
|
|
58
|
+
norm = t.strip().upper()
|
|
59
|
+
if re.match(r"^BAB\s+(?:I|1|01)\b", norm):
|
|
60
|
+
return True
|
|
61
|
+
if re.match(r"^(?:1\.|1\s+|I\.)", norm):
|
|
62
|
+
return True
|
|
63
|
+
if norm.startswith("PENDAHULUAN"):
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def is_chapter_2(t):
|
|
69
|
+
norm = t.strip().upper()
|
|
70
|
+
if re.match(r"^BAB\s+(?:II|2|02)\b", norm):
|
|
71
|
+
return True
|
|
72
|
+
if re.match(r"^(?:2\.|2\s+|II\.)", norm):
|
|
73
|
+
return True
|
|
74
|
+
if any(
|
|
75
|
+
norm.startswith(k)
|
|
76
|
+
for k in (
|
|
77
|
+
"PEMBAHASAN",
|
|
78
|
+
"TINJAUAN PUSTAKA",
|
|
79
|
+
"LANDASAN TEORI",
|
|
80
|
+
"METODOLOGI",
|
|
81
|
+
"METODE PENELITIAN",
|
|
82
|
+
)
|
|
83
|
+
):
|
|
84
|
+
return True
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def classify(text):
|
|
89
|
+
t = text.strip()
|
|
90
|
+
if is_front_matter_heading(t):
|
|
91
|
+
return "cover"
|
|
92
|
+
if is_chapter_1(t):
|
|
93
|
+
return "roman"
|
|
94
|
+
if is_chapter_2(t):
|
|
95
|
+
return "decimal-start"
|
|
96
|
+
return "body"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def add_pgnum(sect, fmt, start):
|
|
100
|
+
if "<w:pgNumType" in sect:
|
|
101
|
+
return sect
|
|
102
|
+
tag = f'<w:pgNumType w:fmt="{fmt}" w:start="{start}"/>'
|
|
103
|
+
return sect + tag
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def process(doc):
|
|
107
|
+
matches = list(SECT_IN_PPR.finditer(doc))
|
|
108
|
+
if not matches:
|
|
109
|
+
return doc, 0
|
|
110
|
+
out = []
|
|
111
|
+
last = 0
|
|
112
|
+
changed = 0
|
|
113
|
+
for m in matches:
|
|
114
|
+
out.append(doc[last:m.start()])
|
|
115
|
+
sect = m.group(1)
|
|
116
|
+
kind = classify(next_heading_text(doc, m.end()))
|
|
117
|
+
if kind == "cover":
|
|
118
|
+
sect = FOOTER_REF.sub("", sect)
|
|
119
|
+
changed += 1
|
|
120
|
+
elif kind == "roman":
|
|
121
|
+
sect = add_pgnum(sect, "lowerRoman", "1")
|
|
122
|
+
changed += 1
|
|
123
|
+
elif kind == "decimal-start":
|
|
124
|
+
sect = add_pgnum(sect, "decimal", "1")
|
|
125
|
+
changed += 1
|
|
126
|
+
out.append(f"<w:pPr><w:sectPr>{sect}</w:sectPr></w:pPr>")
|
|
127
|
+
last = m.end()
|
|
128
|
+
out.append(doc[last:])
|
|
129
|
+
return "".join(out), changed
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
src = sys.argv[1]
|
|
134
|
+
dst = sys.argv[2]
|
|
135
|
+
|
|
136
|
+
with zipfile.ZipFile(src, "r") as zin:
|
|
137
|
+
names = zin.namelist()
|
|
138
|
+
items = {n: zin.read(n) for n in names}
|
|
139
|
+
|
|
140
|
+
doc = items["word/document.xml"].decode("utf-8")
|
|
141
|
+
new_doc, changed = process(doc)
|
|
142
|
+
items["word/document.xml"] = new_doc.encode("utf-8")
|
|
143
|
+
|
|
144
|
+
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
|
|
145
|
+
for n in items:
|
|
146
|
+
zout.writestr(n, items[n])
|
|
147
|
+
|
|
148
|
+
print(f"[OK] finalize-docx: {changed} sectPr disesuaikan -> {dst}")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
main()
|