All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 53s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 4s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
สคริปต์ทดลอง make-protected.mjs ตามแนวทาง Token-Based ใน note.md: - เปิด documentProtection readOnly + enforcement ใน settings.xml - ครอบ Content Control ทุกช่องด้วย permStart/permEnd (edGrp=everyone) ให้แก้ได้เฉพาะในช่อง + เติม w:lock=sdtLocked กันลบช่อง - ฝัง custom properties (token/primary-key/owner) ที่ docProps/custom.xml พร้อมแก้ [Content_Types].xml และ _rels/.rels ให้ครบ - verify อ่านไฟล์กลับทุกครั้งหลังเขียน (exit 1 ถ้าไม่ผ่าน) - บันทึกบทเรียน/ผลทดสอบ Word จริง รอบ 1-2 ใน note.md พร้อม T3.docx/output-T3.docx อัปเดตตามผลรัน Co-Authored-By: Claude Code <noreply@anthropic.com>
14 KiB
14 KiB
Docx Template
lib-tmpl.ts เป็นแชร์ไลบารีสำหรับการทำ Template ใช้ Content Control ใน MS Word
วิธีการใช้ API
## Swagger UI — เปิดในเบราว์เซอร์ (ลองยิงจริงผ่านปุ่ม Try it out ได้เลย ไม่ต้อง login)
http://localhost:3000/docedit/api/swagger
## template/test ไว้ทดสอบเท่านั้น
curl.exe http://localhost:3000/docedit/api/template/test
## extract ค่าออกมาจาก content control
curl.exe -X POST http://localhost:3000/docedit/api/template/extract -F "file=@./output-T3.docx"
## render เอกสาร (docx) = template(docx)+data(json)
### อาจจะเพิ่มการเอา template จาก url และ file(local) ยังไม่รู้ว่าทางนั้นจะส่งไฟล์แบบไหนมาให้ เอาแค่นี้ไว้ทดสอบก่อน
curl.exe -X POST http://localhost:3000/docedit/api/template/render -F "template=@./T3.docx" -F "data=@./T2.json" -o output.docx
### data เอาจาก string
curl -X POST http://localhost:3000/docedit/api/template/render \
-F "template=@template.docx" \
-F 'dataJson={"member_name":"นายสมชาย","member_id":"123456"}' \
-o generated-document.docx
data(json) ตอนนี้อยู่ในรูปแบบ
{
"notname": "คำสั่งเรียก ให้ทำคำให้การ",
"nottype": "(ต. ๑)",
"blackno": "คดีหมายเลขดำที่",
"blackyear": "๑๐/๒๕๖๙",
...
}
data(json)น่าจะปรับเป็นแบบนี้เพื่อรองรับการทำ Token-Based Seamless Integration หลักผ่าน API
- ส่วนของ cc จะไปกรอกใน Content Control
- uid,uname,fid,ott,expire สร้างเป็น token ฝังใน custom property ของเอกสาร
{
"uid":"id ผู้มีสิทธิ์แก้ไขเอกสาร",
"uname": "ชื่อผู้มีสิทธิ์แก้ไขเอกสาร สำหรับแสดงผล",
"fid":"file id จะได้เซฟข้อมูลกลับไปที่ Oracle ถูกตารางและหน่วยงาน",
"ott":"อายุของ one time token แบบระยะสั้น",
"expire": "อายุเอกสารแบบระยะยาวหลังเอา ott ไปแลก (optional)",
cc:{
"notname": "คำสั่งเรียก ให้ทำคำให้การ",
"nottype": "(ต. ๑)",
"blackno": "คดีหมายเลขดำที่",
"blackyear": "๑๐/๒๕๖๙",
...
}
}
ที่ต้องทำเพิ่ม
- render ปัจจุบันมีไว้ให้ทางศาลทดสอบ อันที่ใช้จริงหลังกดสร้าง Template เพื่อ Download จะสร้างเอกสารเวอร์ชั่นแรกใน object storage หรือถ้ามีอยู่แล้ว จะเป็นการอัปเดต
- Backend จัดการ version
- อัปเดตข้อมูลและเอกสารกลับระบบเดิม ส่วนนี้ยังไม่ชัดเจนอาจจะทำ mock api เตรียมไว้ก่อน
ทดลอง Restrict Edit + Custom Properties (2026-10-07)
สคริปต์ make-protected.mjs — อ่าน docx → เปิด protection + ฝัง custom properties → เขียนไฟล์ → อ่านกลับ verify ทุกครั้ง
node app/api/template/test/make-protected.mjs [in.docx] [out.docx]
# ไม่ส่ง argument = อ่านและเขียนทับ output-T3.docx (ค่าตัวอย่างฝังอยู่ในตัวสคริปต์ CUSTOM_PROPS)
Restrict Edit (เปิดผ่านโค้ด)
- อยู่ที่
word/settings.xml→<w:documentProtection w:edit="readOnly" w:enforcement="1"/> - T3.docx มี element นี้อยู่เดิมแต่เป็น
w:enforcement="0"(ตั้งค่าไว้ใน Word แต่ไม่ได้กดบังคับใช้) — สคริปต์แค่พลิกเป็น 1 ไม่ต้องแทรก element ใหม่ - บทเรียนจากทดสอบใน Word จริง (2026-10-07):
readOnly+ enforcement ล็อคทั้งเอกสารรวมถึง Content Control ด้วย — CC ไม่ได้ถูกยกเว้นให้แก้อัตโนมัติ ต้องคู่กับข้อถัดไป - กลไกที่ถูกต้อง: ครอบ
w:sdtทุกตัวที่มีw:tagด้วย<w:permStart w:id="N" w:edGrp="everyone"/>…<w:permEnd w:id="N"/>— คือกลไก "Exceptions (optional): Everyone" ที่แผง Restrict Editing ของ Word ใช้เวลาเราเลือกบริเวณแล้วติ๊ก Everyone (บริเวณใน permission range แก้ได้ นอกนั้นล็อค) — แทรกที่ระดับเดียวกับ sdt (inline ในw:pหรือ block ในw:bodyก็ valid ตาม grammar) - สคริปต์เติม
w:lock="sdtLocked"ให้ CC ที่ยังไม่มีด้วย (T3 ขาด acc1, nottitle) — พอช่องถูกเปิดให้แก้ผ่าน permission แล้ว ช่องที่ไม่มี lock จะถูกลบได้ตามไปด้วย - ไม่ใส่ password → ผู้ใช้ปลดเองได้จาก Review → Restrict Editing → Stop Protection — ตั้งใจไว้แบบนี้เพราะ concept คือ token ใน custom property เป็นตัวกำกับสิทธิ์จริง ส่วน protection เป็นแค่เกราะกันแก้พลาด (ถ้าจะใส่ password ต้องคำนวณ legacy hash: SHA-1 ของ password+salt วนตาม
w:cryptSpinCountตาม ECMA-376 — ยังไม่ทำ)
ฝังข้อมูลใน Property เอกสาร (token / primary-key / owner)
ทางเลือกที่ศึกษา:
docProps/custom.xml(custom properties) — ที่เลือกใช้ เห็นได้ใน Word: File → Info → Properties → Advanced Properties → แท็บ Custom; add-in ฝั่ง Word (Office.js) อ่าน/เขียนได้ผ่านDocument.properties.customProperties(WordApi 1.3+)- ไฟล์ที่ไม่มี part นี้อยู่ (กรณีปกติของ docx) ต้องทำครบ 3 อย่างพร้อมกัน ไม่งั้น Word ถือว่าไฟล์เสีย:
- สร้าง
docProps/custom.xml— property แต่ละตัว:fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"คงที่ +pid≥ 2 ไม่ซ้ำ +name+ ค่า string ในvt:lpwstr [Content_Types].xmlเพิ่ม<Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/>_rels/.relsเพิ่ม Relationship ชนิด.../relationships/custom-propertiesTargetdocProps/custom.xml
- สร้าง
- ไฟล์ที่ไม่มี part นี้อยู่ (กรณีปกติของ docx) ต้องทำครบ 3 อย่างพร้อมกัน ไม่งั้น Word ถือว่าไฟล์เสีย:
docProps/core.xml(core properties: title/subject/creator/description) — แก้ง่าย เห็นในหน้า Properties ปกติ เหมาะกับข้อมูลที่แสดงต่อผู้ใช้ ไม่ใช่ tokenw:docVarในword/settings.xml(document variables) — ไม่โผล่ใน UI เลย อ่านได้ผ่าน VBA/VSTO (Document.Variables) แต่ Word JS API ไม่มีทางตรง — ไม่เลือก
gotcha ที่เจอ: XMLSerializer ของ @xmldom/xmldom คง <?xml ...?> declaration ไว้ในผลลัพธ์อยู่แล้ว — ห้าม prepend เพิ่มเอง จะได้ declaration ซ้อนเป็น XML invalid (Word/node อ่านไม่ได้)
extract บอก alignment รายช่อง — key <tag>@align (2026-10-07) — ถอดออกแล้วตามคำขอ (ยังไม่เอา)
- เคย implement ใน
lib-tmpl.ts+ speclib/openapi.tsแล้วถอดกลับสถานะเดิม — extract ปัจจุบันคืนแค่{ tag: text }เหมือนเดิม - แนวทางที่พิสูจน์แล้วว่าใช้ได้ (เผื่อกลับมาทำภายหลัง): คืน key
<tag>@align(ค่า w:jc เช่นthaiDistribute) เฉพาะช่องที่ต่างจาก default ของเอกสาร โดย resolve:w:pPr/w:jcตรงของย่อหน้า → style ที่ใช้ (ไล่w:pStyle→w:basedOn) →docDefaults("left"/"start" นับเป็นไม่ตั้ง) — บริบทย่อหน้าของช่อง: CC block =w:pแรกในw:sdtContent/ CC inline =w:pที่ครอบ sdt - ผลทดสอบตอน implement: direct pPr ชนะ style ✓ inline/block จับได้ทั้งคู่ ✓ และ T3 จัดช่อง 8 ตัวผ่าน style อยู่แล้ว (nottype/blackyear/redyear/acc1/acc2/groupname/detail1/pname — ชิดขวา/กลาง) — ช่องเหล่านี้จะโผล่
@alignตามจริง
จัด paragraph แบบ Thai Distributed — ตัดสินใจ: จัดเองใน Word ตอนทำ template (2026-10-07)
- ค่า OOXML คือ
<w:jc w:val="thaiDistribute"/>ในw:pPr(ใน Word UI: Paragraph → Alignment → Thai Distributed, ทางลัด Ctrl+Shift+J) — ต่างจากdistributeธรรมดาตรงที่จัดกระจายช่องว่างระหว่างคำเป็นหลัก ไม่บีบยืดตัวอักษร/สระ-วรรณยุกต์ - ไม่ตั้งผ่านโค้ด — ผู้ออกแบบ template จัดเองใน Word ตอนทำ template ทางเดียวกับที่ตั้งฟอนต์/ย่อหน้าอื่น ๆ (เคยทดลองตั้ง default ทั้งเอกสารผ่าน
styles.xml→w:docDefaults/w:pPrDefault/w:pPrแล้วถอดออกตามคำขอ) - วิธีจัดเองใน Word:
- รายย่อหน้า/ราย CC: คลิกในย่อหน้า →
Ctrl+Shift+Jหรือ Home → Paragraph launcher → Alignment เลือก Thai Distributed - ทั้งเอกสาร: คลิกขวา style Normal → Modify → Format → Paragraph → Thai Distributed
- จัด "เฉพาะช่อง CC" ได้เฉพาะ CC แบบ block (มี
w:pในw:sdtContentของตัวเอง เช่นdetail1) — CC แบบ inline อยู่ในบรรทัดเดียวกับข้อความอื่น การจัดมีผลทั้งบรรทัด
- รายย่อหน้า/ราย CC: คลิกในย่อหน้า →
- จุดสำคัญต่อ render pipeline:
insertBlockContentของ lib-tmpl clonew:pPrของย่อหน้าแรกให้ทุกบรรทัดที่แทนค่า — จัดไว้ใน CC ตั้งแต่ต้นค่าจะสืบทอดไปทุกบรรทัดที่ render ใส่ข้อมูลอัตโนมัติ ไม่ต้องทำอะไรฝั่ง render - (อ้างอิงเชิงเทคนิคกรณีต้องตั้งผ่านโค้ดภายหลัง:
w:jcในw:pPrเป็น fixed sequence — ต้องอยู่หลังw:autoSpaceDN/w:spacing/w:indและก่อนw:textDirection/w:rPr/w:sectPr)
ผล verify (อัตโนมัติทุกครั้งที่รันสคริปต์)
documentProtectionเปิดใช้ (readOnly + enforcement=1) ✓docProps/custom.xmlมี + relationship + content-type override ครบ ✓- Content Control 27 ตัวครบเดิม (plain 26 = มี
w:text, rich 1 =detail1) ✓ - permission ranges: permStart/permEnd ครบตามจำนวน CC ที่มี tag (27/27) ✓
- ทุก CC มี
w:lock="sdtLocked"(กันลบช่อง) ✓ extractContentControls(lib-tmpl) อ่านคืนได้ทุก tag ตรงกับ output.docx ก่อน protect 100% ✓
ผลทดสอบใน Word จริง (2026-10-07 รอบ 1)
- ✅ แถบเหลือง protection ขึ้น / แก้นอก CC ไม่ได้ / ลบ CC ไม่ได้
- ❌ B5: CC แก้ไม่ได้ เพราะ readOnly ล็อคทั้งเอกสาร → แก้ด้วย permStart/permEnd ตามด้านบนแล้ว (รอบ 2)
- ✅ custom properties ครบ / ปลด protection ได้แบบไม่มี password
เหลือตรวจใน Word จริง (รอบ 2)
- เปิด
output-T3.docx→ พิมพ์/แก้ในช่อง CC ต้องได้แล้ว (B5) - ลบ CC ต้องไม่ได้ทุกตัว (รวม acc1, nottitle ที่เติม lock ให้)
- ขั้น E เดิม: F12 เซฟสำเนา → เปิดใหม่ → properties ยังอยู่ครบ
- (ผ่านแล้วทั้งหมด → ย้าย logic ไปเป็นฟังก์ชันใน lib-tmpl.ts แล้วต่อเข้า render pipeline)