| name | docx-generator |
| description | Production-grade DOCX generator that creates valid Microsoft Word (.docx) files from Markdown or structured AST using a safe streaming ZIP engine and minimal but strict OOXML baseline. |
| when_to_use | Use when generating Word (.docx) documents from Markdown or structured AST. Suitable for AI-generated reports, documentation, summaries, or structured outputs that must reliably open in Microsoft Word. |
| tools | jsexec, read, write, bash |
| args | [{"name":"input","description":"Markdown string or structured AST used to generate DOCX","required":true},{"name":"output","description":"Output file path for the generated .docx file (default: /generated_document.docx)","required":false},{"name":"mode","description":"Input mode: markdown | ast (default: markdown)","required":false}] |
CODE SKILL — VERBATIM REQUIRED
This skill targets a formal specification (OOXML).
Adaptation is prohibited per C2-A Rule 3.
CRITICAL: Execution Constraints
The code below is the SOLE IMPLEMENTATION — it MUST NOT be replaced, simplified, or reimplemented.
Prohibited deviations:
- Do NOT write your own
crc32() — use ONLY the one in Runnable Entry Point
- Do NOT write your own
ZipStream class — use ONLY the one in Runnable Entry Point
- Do NOT skip any OOXML file from the 10-file baseline
- Do NOT use compression (method 8 / DEFLATE) — STORE mode only
- Do NOT assemble document body XML manually outside
markdownToDocxBody() or astToDocxBody()
Correct Invocation Pattern
- Copy the complete script from
## Runnable Entry Point below
- Replace ONLY
{{input}}, {{output}}, {{mode}} with actual values
- Execute as a single
jsexec call with NO modifications
Argument Details
| Arg | Type | Required | Default | Description |
|---|
input | string | yes | — | Markdown string or structured AST |
output | string | no | /generated_document.docx | Output .docx file path |
mode | string | no | markdown | Input mode: markdown |
Runnable Entry Point (COMPLETE IIFE, DO NOT MODIFY)
(function() {
const fs = require("fs");
const path = require("path");
const CRC32_TABLE = new Int32Array(256);
(function(){for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?(0xEDB88320^(c>>>1)):(c>>>1);CRC32_TABLE[n]=c;}})();
function crc32(buf) {
let crc = 0xFFFFFFFF;
for (let i = 0; i < buf.length; i++)
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ buf[i]) & 0xFF];
return (crc ^ 0xFFFFFFFF) >>> 0;
}
function validateDocx(buf) {
let eocdOff = -1;
for (let i = buf.length - ; i >= ; i--) {
(buf[i]=== && buf[i+]=== && buf[i+]=== && buf[i+]===) {
eocdOff = i; ;
}
}
(eocdOff === -) ();
total = buf.(eocdOff + );
cdOff = buf.(eocdOff + );
pos = cdOff;
errors = [];
( k = ; k < total; k++) {
(buf.(pos) !== ) { errors.(+k); ; }
compMethod = buf.(pos + );
crcStored = buf.(pos + );
uncompSize = buf.(pos + );
nameLen = buf.(pos + );
extraLen = buf.(pos + );
localOff = buf.(pos + );
fname = buf.(pos + , pos + + nameLen).();
(compMethod !== ) errors.();
{
(!fname.()) {
localNameLen = buf.(localOff + );
localExtraLen = buf.(localOff + );
dataStart = localOff + + localNameLen + localExtraLen;
storedData = buf.(dataStart, dataStart + uncompSize);
actualCrc = (storedData);
(actualCrc !== crcStored)
errors.();
}
} (e) { errors.(); }
pos += + nameLen + extraLen;
}
(errors. > )
( + errors.(+e).());
.( + total + );
}
{
() { . = []; . = []; . = ; }
() {
raw = .(data) ? data : .(data, );
nameBuf = .(name, );
fileCrc = (raw);
header = .();
header.(, );
header.(, ); header.(, ); header.(, );
header.(fileCrc, ); header.(raw., );
header.(raw., ); header.(nameBuf., );
local = .([header, nameBuf]);
..(local, raw);
..({ name, : fileCrc, : raw., : . });
. += local. + raw.;
}
() {
central = [];
( e .) {
nameBuf = .(e., );
c = .();
c.(, ); c.(, ); c.(, );
c.(, ); c.(, ); c.(e., );
c.(e., ); c.(e., );
c.(nameBuf., ); c.(, ); c.(, );
c.(, ); c.(, ); c.(, ); c.(e., );
central.(c, nameBuf);
}
centralBuf = .(central);
eocd = .();
eocd.(, ); eocd.(.., );
eocd.(.., ); eocd.(centralBuf., );
eocd.(., );
.([...., centralBuf, eocd]);
}
}
() {
imgOverrides = images.( ).();
;
}
() {
;
}
() {
imgRels = images.( ).();
;
}
() {
imgNs = imageCount >
?
: ;
;
}
() {
;
}
(){ ;}
(){ ;}
(){
;
}
(){
;
}
(){
bulletNums = .({:},).();
decimalNums = .({:},).();
;
}
() {
clean = (s).(, );
clean.(,).(,).(,);
}
() {
parts = [];
(opts.) parts.();
(opts.) parts.();
(opts.) parts.();
parts.();
;
}
() {
pPr = style ? : ;
;
}
() {
left = * (indentLevel || );
;
}
() {
runs = [];
i = ;
(i < text.) {
(text[i] === ) { i++; ; }
(text[i]===&&text[i+]===&&text[i+]===) {
end=text.(,i+); (end!==-){runs.((text.(i+,end),{:,:}));i=end+;;}
runs.(());i++;;
}
(text[i]===&&text[i+]===) {
end=text.(,i+); (end!==-){runs.((text.(i+,end),{:}));i=end+;;}
runs.(());i++;;
}
(text[i]===&&text[i+]!==) {
end=text.(,i+); (end!==-){runs.((text.(i+,end),{:}));i=end+;;}
runs.(());i++;;
}
(text[i]===) {
end=text.(,i+); (end!==-){runs.((text.(i+,end),{:}));i=end+;;}
runs.(());i++;;
}
(text[i]===) {
isImage=i>&&text[i-]===;
isImageWS=i>&&text[i-]===&&text[i-]===;
(isImage||isImageWS) {
close=text.(,i); op=text.(,close); cp=text.(,op);
(close!==-&&op===close+&&cp!==-) {
alt=text.(i+,close); imgPath=text.(op+,cp);
(isImageWS)runs.();
runs.(+.(.({alt,:imgPath})).()+);
i=cp+;;
}
}
close=text.(,i); op=text.(,close); cp=text.(,op);
(close!==-&&op===close+&&cp!==-){
runs.();
i=cp+;;
}
runs.(());i++;;
}
j=i; (j<text.&&text[j]!==&&text[j]!==&&text[j]!==)j++;
(j>i)runs.((text.(i,j)));
i=j;
}
runs.();
}
() {
maxCols = .(headers., ...dataRows.(r.), );
colWidth = .(/maxCols);
gridCols = .({:maxCols},).();
border = ;
(){
shd=isH?:;
rPr=isH?:;
;}
=r=>[...r,...(.(,maxCols-r.)).()].(,maxCols);
hRow=;
dRows=dataRows.().();
;
}
() {
lines=md.(); body=[]; i=;
bulletNumId=,decimalNumId=;
currentListType=,currentNumId=;
(){currentListType=;currentNumId=;}
(i<lines.){
line=lines[i];
(line.()===){();body.();i++;;}
hMatch=line.();
(hMatch){(); lv=.(hMatch[].,);body.(((hMatch[]),));i++;;}
(.(line.())){();body.();i++;;}
bqMatch=line.();
(bqMatch){
indent=bqMatch[].().-;
body.((bqMatch[],indent));
();i++;;
}
olMatch=line.();
(olMatch){ indent=.(olMatch[]./);(currentListType!==){currentListType=;currentNumId=decimalNumId++;}body.();i++;;}
ulMatch=line.();
(ulMatch){ indent=.(ulMatch[]./);(currentListType!==){currentListType=;currentNumId=bulletNumId++;}body.();i++;;}
(line.().()){
(); i++;
codeLines=[];
lang=;
firstLine=lines[i]||;
langMatch=firstLine.();
(!langMatch||[,,,,,,,,,,,,,,].(langMatch[].())){
(langMatch) lang=langMatch[];
}
(langMatch) i++;
(i<lines.&&!lines[i].().()){codeLines.(lines[i]);i++;}
(i<lines.) i++;
body.();
( cl codeLines){body.();}
;
}
(line.().()&&line.().()){
(); tblLines=[];
(i<lines.&&lines[i].().()&&lines[i].().()){tblLines.(lines[i]);i++;}
(tblLines.>=){
=l=>l.().(,-).().(c.());
hRow=(tblLines[]);
sRow=(tblLines[]);
(sRow.(.(c))){
dRows=tblLines.().(pr);
body.((hRow,dRows));body.();
;
}
}
i=tblLines[]?lines.(tblLines[]):i;
}
();
body.(((line)));
i++;
}
body.();
}
() {
h=buf; {
(ext===&&h[]===&&h[]===) {:h.(),:h.()};
(ext===||ext===) {
off=; (off<h.-){(h[off]===&&(h[off+]>=&&h[off+]<=)){{:h.(off+),:h.(off+)};} off+=h.(off+)+;} }
(ext===) {:h.(),:h.()};
(ext===) {:h.(),:.(h.())};
}(_){}
;
}
(){
(!px) {:,:};
=; scale=px.>?/px.:;
{:.(px.*scale*),:.(px.*scale*)};
}
(){
m={:,:,:,:,:};
m[ext]||;
}
(){
h=buf.(,);
(ext===) h[]===&&h[]===;
(ext===||ext===) h[]===&&h[]===&&h[]===;
;
}
(){
;
}
() {
outDir = path.(output);
{ fs.(outDir, { : }); } (e) {}
zip = ();
body;
(mode === ) body = ((input));
(mode === ) body = (input);
body = (((input)));
images = [];
body = body.(, {
{
info = .(.(b64, ).());
imgPath = info.;
imgBuf;
{ imgBuf = fs.(imgPath); } (e) { ; }
ext = path.(imgPath).().() || ;
(!(imgBuf, ext)) ;
name = ;
rId = ;
images.({ name, ext, : (ext), rId, : imgBuf });
emu = ((imgBuf, ext));
(rId, info., emu., emu.);
} (e) { ; }
});
zip.(, (images));
zip.(, ());
zip.(, (body, images.));
zip.(, (images));
zip.(, ());
zip.(, ());
zip.(, ());
zip.(, ());
zip.(, ());
zip.(, ());
( img images) zip.(, img.);
buf = zip.();
(buf);
fs.(output, buf);
.(, output, buf.);
}
() {
(!.(ast)) (((ast)));
bulletNumId = , decimalNumId = ;
parts = [];
( node ast) {
(node.) {
: parts.(((node.||), )); ;
: parts.(((node.||))); ;
: parts.((node.||, node.||)); ;
: (node.&&node.){parts.((node.,node.));parts.();} ;
: (node.) parts.(); ;
: parts.(); ;
: {
listNumId = node. ? (decimalNumId++) : (bulletNumId++);
( item (node.||[])) {
itemText = item === ? item : (item.||);
itemIndent = item === ? (item.||) : ;
parts.();
}
} ;
}
}
parts.();
}
input = ;
output = .(, ) || ;
mode = (.(, ) || ).();
(input, mode, output);
})();
Usage Notes
Markdown Feature Support
| Feature | Syntax | Rendered Output |
|---|
| Headings 1-6 | #–###### | Calibri, colored (Office theme) |
| Bold | **text** | Bold text |
| Italic | *text* | Italic text |
| Bold+Italic | ***text*** | Bold + italic |
| Inline Code | `code` | Courier New, monospace |
| Bullet List | - item | ● / ○ / ■ (3 nesting levels) |
| Ordered List | 1. item | 1. / a. / i. (3 nesting levels) |
| Blockquote | > text | Left-indented with left border |
| Code Block | ```lang ... ``` | Gray background, monospace font |
| Table | | cell | cell | | Border + styled header row |
| Horizontal Rule | --- | Thin bottom border paragraph |
| Hyperlink | [text](url) | Blue underlined (Hyperlink style) |
| Image |  | Embedded PNG/JPG/GIF/BMP (SVG not supported) |
Output Validation
After generation, the ZIP is automatically validated before writing:
- EOCD integrity — End of Central Directory Record must exist
- CRC-32 checksum — every entry's stored CRC must match computed CRC
- Compression method — all entries must use method 0 (STORE, no DEFLATE)
- 10 baseline OOXML files —
[Content_Types].xml, _rels/.rels, word/document.xml, word/styles.xml, word/settings.xml, word/webSettings.xml, word/fontTable.xml, word/theme/theme1.xml, word/numbering.xml, word/_rels/document.xml.rels
Image Support
Images are referenced via  in Markdown. Supported formats:
PNG, JPG/JPEG, GIF, BMP. Images are auto-detected for dimensions and embedded as STORE entries in the ZIP.
Unreadable files or remote URLs produce [Image: ...] fallback text.
Common Pitfalls
⚠️ Embedding markdown in JavaScript template literals: The {{input}} replacement inserts markdown directly into a template literal (`). If your markdown contains backtick characters (code blocks or inline code), they will prematurely terminate the template literal. Always pre-process markdown before embedding: replace(//g, '\') and replace(/\$\{/g, '\\\$\{'). Alternatively, use a plain string with \n for newlines.
⚠️ Large images in Wasm environments: CRC32 uses hardware-optimized table lookup (8× faster than bit-by-bit). validateDocx() skips image CRC re-verification since ZipStream.writeFile() already computes and stores the correct CRC. Generation should handle 10+ MB of images efficiently.
⚠️ No style control: The docx-generator uses built-in styles (Normal, Heading1-4, Hyperlink, ListBullet, ListNumber). Custom font sizes, colors, or paragraph spacing are not configurable through the Markdown input.
C3 Verification
| Check Item | Constraint | Common Omission | Method |
|---|
| ZIP Integrity | EOCD + CD signatures valid | Corrupted ZIP | unzip -t <file> |
[Content_Types].xml | All parts declared | Missing Override | Check XML |
_rels/.rels | rId1 → officeDocument | Missing relationship | Verify Target |
word/document.xml | Valid OOXML w:document | Malformed XML | XML well-formed |
word/_rels/document.xml.rels | Image rIds present if images used | Missing image rels | Check r:embed |
| Image files | word/media/ present | Missing binary data | Check media count |
| Compression | Method 0 (STORE only) | Accidental DEFLATE | Verify method |
| Inline formatting | Bold/italic/code spans render | Formatting lost | Inspect w:rPr |
| Lists | Nested numbering present | Flat list | Check w:numPr levels |
| No content loss | All input text present | Content truncated | Line-by-line diff |