remark-docx 0.1.5 → 0.1.7
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/README.md +4 -3
- package/lib/index.js +156 -77
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +157 -78
- package/lib/index.mjs.map +1 -1
- package/lib/latex.d.ts +1 -2
- package/lib/utils.d.ts +1 -2
- package/package.json +38 -34
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# remark-docx
|
|
2
2
|
|
|
3
|
-
  
|
|
3
|
+
   
|
|
4
4
|
|
|
5
5
|
[remark](https://github.com/remarkjs/remark) plugin to compile markdown to docx (Microsoft Word, Office Open XML).
|
|
6
6
|
|
|
@@ -100,10 +100,11 @@ If you find a problem, feel free to create an [issue](https://github.com/inokawa
|
|
|
100
100
|
|
|
101
101
|
### Making a Pull Request
|
|
102
102
|
|
|
103
|
-
1.
|
|
103
|
+
1. Fork this repo.
|
|
104
104
|
2. Run `npm install`.
|
|
105
105
|
3. Commit your fix.
|
|
106
|
-
4.
|
|
106
|
+
4. Add tests to cover the fix.
|
|
107
|
+
5. Make a PR and confirm all the CI checks passed.
|
|
107
108
|
|
|
108
109
|
## Related projects
|
|
109
110
|
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var tslib = require('tslib');
|
|
4
3
|
var unistUtilVisit = require('unist-util-visit');
|
|
5
4
|
var docx = require('docx');
|
|
6
5
|
var unifiedLatexUtilParse = require('@unified-latex/unified-latex-util-parse');
|
|
7
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
8
10
|
const unreachable = (_) => {
|
|
9
11
|
throw new Error("unreachable");
|
|
10
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
11
16
|
function invariant(cond, message) {
|
|
12
17
|
if (!cond)
|
|
13
18
|
throw new Error(message);
|
|
@@ -337,6 +342,9 @@ const mapNode = (n, runs) => {
|
|
|
337
342
|
}
|
|
338
343
|
return [];
|
|
339
344
|
};
|
|
345
|
+
/**
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
340
348
|
const parseLatex = (value) => {
|
|
341
349
|
const parsed = unifiedLatexUtilParse.parseMath(value);
|
|
342
350
|
const paragraphs = [[]];
|
|
@@ -419,11 +427,13 @@ const DEFAULT_NUMBERINGS = [
|
|
|
419
427
|
},
|
|
420
428
|
},
|
|
421
429
|
];
|
|
422
|
-
const mdastToDocx = (node, { output = "buffer", title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, }, images) =>
|
|
430
|
+
const mdastToDocx = async (node, { output = "buffer", title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, }, images) => {
|
|
431
|
+
const footnoteRegistry = {};
|
|
423
432
|
const { nodes, footnotes } = convertNodes(node.children, {
|
|
424
433
|
deco: {},
|
|
425
434
|
images,
|
|
426
435
|
indent: 0,
|
|
436
|
+
footnoteRegistry,
|
|
427
437
|
});
|
|
428
438
|
const doc = new docx.Document({
|
|
429
439
|
title,
|
|
@@ -448,7 +458,7 @@ const mdastToDocx = (node, { output = "buffer", title, subject, creator, keyword
|
|
|
448
458
|
});
|
|
449
459
|
switch (output) {
|
|
450
460
|
case "buffer":
|
|
451
|
-
const bufOut =
|
|
461
|
+
const bufOut = await docx.Packer.toBuffer(doc);
|
|
452
462
|
// feature detection instead of environment detection, but if Buffer exists
|
|
453
463
|
// it's probably Node. If not, return the Uint8Array that JSZip returns
|
|
454
464
|
// when it doesn't detect a Node environment.
|
|
@@ -456,27 +466,45 @@ const mdastToDocx = (node, { output = "buffer", title, subject, creator, keyword
|
|
|
456
466
|
case "blob":
|
|
457
467
|
return docx.Packer.toBlob(doc);
|
|
458
468
|
}
|
|
459
|
-
}
|
|
469
|
+
};
|
|
470
|
+
const getOrCreateFootnoteId = (identifier, registry) => {
|
|
471
|
+
if (!(identifier in registry)) {
|
|
472
|
+
registry[identifier] = Object.keys(registry).length + 1;
|
|
473
|
+
}
|
|
474
|
+
return registry[identifier];
|
|
475
|
+
};
|
|
460
476
|
const convertNodes = (nodes, ctx) => {
|
|
461
477
|
const results = [];
|
|
462
478
|
let footnotes = {};
|
|
463
479
|
for (const node of nodes) {
|
|
464
480
|
switch (node.type) {
|
|
465
|
-
case "paragraph":
|
|
466
|
-
|
|
481
|
+
case "paragraph": {
|
|
482
|
+
const { paragraph, footnotes: nestedFootnotes } = buildParagraph(node, ctx);
|
|
483
|
+
results.push(paragraph);
|
|
484
|
+
footnotes = { ...footnotes, ...nestedFootnotes };
|
|
467
485
|
break;
|
|
468
|
-
|
|
469
|
-
|
|
486
|
+
}
|
|
487
|
+
case "heading": {
|
|
488
|
+
const { heading, footnotes: nestedFootnotes } = buildHeading(node, ctx);
|
|
489
|
+
results.push(heading);
|
|
490
|
+
footnotes = { ...footnotes, ...nestedFootnotes };
|
|
470
491
|
break;
|
|
492
|
+
}
|
|
471
493
|
case "thematicBreak":
|
|
472
494
|
results.push(buildThematicBreak());
|
|
473
495
|
break;
|
|
474
|
-
case "blockquote":
|
|
475
|
-
|
|
496
|
+
case "blockquote": {
|
|
497
|
+
const { nodes: blockquoteNodes, footnotes: nestedFootnotes } = buildBlockquote(node, ctx);
|
|
498
|
+
results.push(...blockquoteNodes);
|
|
499
|
+
footnotes = { ...footnotes, ...nestedFootnotes };
|
|
476
500
|
break;
|
|
477
|
-
|
|
478
|
-
|
|
501
|
+
}
|
|
502
|
+
case "list": {
|
|
503
|
+
const { nodes: listNodes, footnotes: nestedFootnotes } = buildList(node, ctx);
|
|
504
|
+
results.push(...listNodes);
|
|
505
|
+
footnotes = { ...footnotes, ...nestedFootnotes };
|
|
479
506
|
break;
|
|
507
|
+
}
|
|
480
508
|
case "listItem":
|
|
481
509
|
invariant(false, "unreachable");
|
|
482
510
|
case "table":
|
|
@@ -501,9 +529,11 @@ const convertNodes = (nodes, ctx) => {
|
|
|
501
529
|
case "definition":
|
|
502
530
|
// FIXME: unimplemented
|
|
503
531
|
break;
|
|
504
|
-
case "footnoteDefinition":
|
|
505
|
-
|
|
532
|
+
case "footnoteDefinition": {
|
|
533
|
+
const footnoteId = getOrCreateFootnoteId(node.identifier, ctx.footnoteRegistry || {});
|
|
534
|
+
footnotes[footnoteId] = buildFootnoteDefinition(node, ctx);
|
|
506
535
|
break;
|
|
536
|
+
}
|
|
507
537
|
case "text":
|
|
508
538
|
results.push(buildText(node.value, ctx.deco));
|
|
509
539
|
break;
|
|
@@ -511,8 +541,12 @@ const convertNodes = (nodes, ctx) => {
|
|
|
511
541
|
case "strong":
|
|
512
542
|
case "delete": {
|
|
513
543
|
const { type, children } = node;
|
|
514
|
-
const { nodes } = convertNodes(children,
|
|
544
|
+
const { nodes, footnotes: nestedFootnotes } = convertNodes(children, {
|
|
545
|
+
...ctx,
|
|
546
|
+
deco: { ...ctx.deco, [type]: true },
|
|
547
|
+
});
|
|
515
548
|
results.push(...nodes);
|
|
549
|
+
footnotes = { ...footnotes, ...nestedFootnotes };
|
|
516
550
|
break;
|
|
517
551
|
}
|
|
518
552
|
case "inlineCode":
|
|
@@ -522,9 +556,12 @@ const convertNodes = (nodes, ctx) => {
|
|
|
522
556
|
case "break":
|
|
523
557
|
results.push(buildBreak());
|
|
524
558
|
break;
|
|
525
|
-
case "link":
|
|
526
|
-
|
|
559
|
+
case "link": {
|
|
560
|
+
const { link, footnotes: nestedFootnotes } = buildLink(node, ctx);
|
|
561
|
+
results.push(link);
|
|
562
|
+
footnotes = { ...footnotes, ...nestedFootnotes };
|
|
527
563
|
break;
|
|
564
|
+
}
|
|
528
565
|
case "image":
|
|
529
566
|
results.push(buildImage(node, ctx.images));
|
|
530
567
|
break;
|
|
@@ -534,12 +571,14 @@ const convertNodes = (nodes, ctx) => {
|
|
|
534
571
|
case "imageReference":
|
|
535
572
|
// FIXME: unimplemented
|
|
536
573
|
break;
|
|
537
|
-
case "footnote":
|
|
538
|
-
|
|
574
|
+
case "footnote": {
|
|
575
|
+
const { footnoteRef, footnoteData } = buildFootnote(node, ctx);
|
|
576
|
+
results.push(footnoteRef);
|
|
577
|
+
footnotes = { ...footnotes, ...footnoteData };
|
|
539
578
|
break;
|
|
579
|
+
}
|
|
540
580
|
case "footnoteReference":
|
|
541
|
-
|
|
542
|
-
results.push(buildFootnoteReference(node));
|
|
581
|
+
results.push(buildFootnoteReference(node, ctx));
|
|
543
582
|
break;
|
|
544
583
|
case "math":
|
|
545
584
|
results.push(...buildMath(node));
|
|
@@ -559,60 +598,69 @@ const convertNodes = (nodes, ctx) => {
|
|
|
559
598
|
};
|
|
560
599
|
const buildParagraph = ({ children }, ctx) => {
|
|
561
600
|
const list = ctx.list;
|
|
562
|
-
const { nodes } = convertNodes(children, ctx);
|
|
563
|
-
const paragraphChildren = [...nodes];
|
|
601
|
+
const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
|
|
564
602
|
if (list && list.checked != null) {
|
|
565
|
-
|
|
603
|
+
nodes.unshift(new docx.CheckBox({
|
|
566
604
|
checked: list.checked,
|
|
567
605
|
checkedState: { value: "2611" },
|
|
568
606
|
uncheckedState: { value: "2610" },
|
|
569
607
|
}));
|
|
570
608
|
}
|
|
571
|
-
return
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
609
|
+
return {
|
|
610
|
+
paragraph: new docx.Paragraph({
|
|
611
|
+
children: nodes,
|
|
612
|
+
indent: ctx.indent > 0
|
|
613
|
+
? {
|
|
614
|
+
start: docx.convertInchesToTwip(INDENT * ctx.indent),
|
|
615
|
+
}
|
|
616
|
+
: undefined,
|
|
617
|
+
...(list &&
|
|
618
|
+
(list.ordered
|
|
619
|
+
? {
|
|
620
|
+
numbering: {
|
|
621
|
+
reference: ORDERED_LIST_REF,
|
|
622
|
+
level: list.level,
|
|
623
|
+
},
|
|
624
|
+
}
|
|
625
|
+
: {
|
|
626
|
+
bullet: {
|
|
627
|
+
level: list.level,
|
|
628
|
+
},
|
|
629
|
+
})),
|
|
630
|
+
}),
|
|
631
|
+
footnotes: nestedFootnotes,
|
|
632
|
+
};
|
|
588
633
|
};
|
|
589
634
|
const buildHeading = ({ children, depth }, ctx) => {
|
|
590
|
-
let
|
|
635
|
+
let headingLevel;
|
|
591
636
|
switch (depth) {
|
|
592
637
|
case 1:
|
|
593
|
-
|
|
638
|
+
headingLevel = docx.HeadingLevel.TITLE;
|
|
594
639
|
break;
|
|
595
640
|
case 2:
|
|
596
|
-
|
|
641
|
+
headingLevel = docx.HeadingLevel.HEADING_1;
|
|
597
642
|
break;
|
|
598
643
|
case 3:
|
|
599
|
-
|
|
644
|
+
headingLevel = docx.HeadingLevel.HEADING_2;
|
|
600
645
|
break;
|
|
601
646
|
case 4:
|
|
602
|
-
|
|
647
|
+
headingLevel = docx.HeadingLevel.HEADING_3;
|
|
603
648
|
break;
|
|
604
649
|
case 5:
|
|
605
|
-
|
|
650
|
+
headingLevel = docx.HeadingLevel.HEADING_4;
|
|
606
651
|
break;
|
|
607
652
|
case 6:
|
|
608
|
-
|
|
653
|
+
headingLevel = docx.HeadingLevel.HEADING_5;
|
|
609
654
|
break;
|
|
610
655
|
}
|
|
611
|
-
const { nodes } = convertNodes(children, ctx);
|
|
612
|
-
return
|
|
613
|
-
heading
|
|
614
|
-
|
|
615
|
-
|
|
656
|
+
const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
|
|
657
|
+
return {
|
|
658
|
+
heading: new docx.Paragraph({
|
|
659
|
+
heading: headingLevel,
|
|
660
|
+
children: nodes,
|
|
661
|
+
}),
|
|
662
|
+
footnotes: nestedFootnotes,
|
|
663
|
+
};
|
|
616
664
|
};
|
|
617
665
|
const buildThematicBreak = (_) => {
|
|
618
666
|
return new docx.Paragraph({
|
|
@@ -620,22 +668,31 @@ const buildThematicBreak = (_) => {
|
|
|
620
668
|
});
|
|
621
669
|
};
|
|
622
670
|
const buildBlockquote = ({ children }, ctx) => {
|
|
623
|
-
const { nodes } = convertNodes(children,
|
|
624
|
-
return nodes;
|
|
671
|
+
const { nodes, footnotes: nestedFootnotes } = convertNodes(children, { ...ctx, indent: ctx.indent + 1 });
|
|
672
|
+
return { nodes, footnotes: nestedFootnotes };
|
|
625
673
|
};
|
|
626
674
|
const buildList = ({ children, ordered, start: _start, spread: _spread }, ctx) => {
|
|
627
675
|
const list = {
|
|
628
676
|
level: ctx.list ? ctx.list.level + 1 : 0,
|
|
629
677
|
ordered: !!ordered,
|
|
630
678
|
};
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
679
|
+
let allFootnotes = {};
|
|
680
|
+
const allNodes = children.flatMap((item) => {
|
|
681
|
+
const { nodes, footnotes: itemFootnotes } = buildListItem(item, {
|
|
682
|
+
...ctx,
|
|
683
|
+
list,
|
|
684
|
+
});
|
|
685
|
+
allFootnotes = { ...allFootnotes, ...itemFootnotes };
|
|
686
|
+
return nodes;
|
|
687
|
+
});
|
|
688
|
+
return { nodes: allNodes, footnotes: allFootnotes };
|
|
635
689
|
};
|
|
636
690
|
const buildListItem = ({ children, checked, spread: _spread }, ctx) => {
|
|
637
|
-
const { nodes } = convertNodes(children,
|
|
638
|
-
|
|
691
|
+
const { nodes, footnotes: nestedFootnotes } = convertNodes(children, {
|
|
692
|
+
...ctx,
|
|
693
|
+
...(ctx.list && { list: { ...ctx.list, checked: checked !== null && checked !== void 0 ? checked : undefined } }),
|
|
694
|
+
});
|
|
695
|
+
return { nodes, footnotes: nestedFootnotes };
|
|
639
696
|
};
|
|
640
697
|
const buildTable = ({ children, align }, ctx) => {
|
|
641
698
|
const cellAligns = align === null || align === void 0 ? void 0 : align.map((a) => {
|
|
@@ -712,11 +769,14 @@ const buildBreak = (_) => {
|
|
|
712
769
|
return new docx.TextRun({ text: "", break: 1 });
|
|
713
770
|
};
|
|
714
771
|
const buildLink = ({ children, url, title: _title }, ctx) => {
|
|
715
|
-
const { nodes } = convertNodes(children, ctx);
|
|
716
|
-
return
|
|
717
|
-
link:
|
|
718
|
-
|
|
719
|
-
|
|
772
|
+
const { nodes, footnotes: nestedFootnotes } = convertNodes(children, ctx);
|
|
773
|
+
return {
|
|
774
|
+
link: new docx.ExternalHyperlink({
|
|
775
|
+
link: url,
|
|
776
|
+
children: nodes,
|
|
777
|
+
}),
|
|
778
|
+
footnotes: nestedFootnotes,
|
|
779
|
+
};
|
|
720
780
|
};
|
|
721
781
|
const buildImage = ({ url, title: _title, alt: _alt }, images) => {
|
|
722
782
|
const img = images[url];
|
|
@@ -731,23 +791,42 @@ const buildImage = ({ url, title: _title, alt: _alt }, images) => {
|
|
|
731
791
|
});
|
|
732
792
|
};
|
|
733
793
|
const buildFootnote = ({ children }, ctx) => {
|
|
734
|
-
//
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
794
|
+
// Generate auto ID based on current registry size to ensure uniqueness
|
|
795
|
+
const registry = ctx.footnoteRegistry || {};
|
|
796
|
+
const autoId = `inline-${Object.keys(registry).length + 1}`;
|
|
797
|
+
const footnoteId = getOrCreateFootnoteId(autoId, registry);
|
|
798
|
+
const footnoteContent = children.map((node) => {
|
|
799
|
+
// Convert each node and extract the first result as a paragraph
|
|
800
|
+
const { nodes } = convertNodes([node], ctx);
|
|
801
|
+
if (nodes[0] instanceof docx.Paragraph) {
|
|
802
|
+
return nodes[0];
|
|
803
|
+
}
|
|
804
|
+
// For non-paragraph content, wrap in a paragraph
|
|
805
|
+
return new docx.Paragraph({ children: nodes });
|
|
738
806
|
});
|
|
807
|
+
return {
|
|
808
|
+
footnoteRef: new docx.FootnoteReferenceRun(footnoteId),
|
|
809
|
+
footnoteData: {
|
|
810
|
+
[footnoteId]: { children: footnoteContent },
|
|
811
|
+
},
|
|
812
|
+
};
|
|
739
813
|
};
|
|
740
814
|
const buildFootnoteDefinition = ({ children }, ctx) => {
|
|
741
815
|
return {
|
|
742
816
|
children: children.map((node) => {
|
|
817
|
+
// Convert each node and extract the first result as a paragraph
|
|
743
818
|
const { nodes } = convertNodes([node], ctx);
|
|
744
|
-
|
|
819
|
+
if (nodes[0] instanceof docx.Paragraph) {
|
|
820
|
+
return nodes[0];
|
|
821
|
+
}
|
|
822
|
+
// For non-paragraph content, wrap in a paragraph
|
|
823
|
+
return new docx.Paragraph({ children: nodes });
|
|
745
824
|
}),
|
|
746
825
|
};
|
|
747
826
|
};
|
|
748
|
-
const buildFootnoteReference = ({ identifier }) => {
|
|
749
|
-
|
|
750
|
-
return new docx.FootnoteReferenceRun(
|
|
827
|
+
const buildFootnoteReference = ({ identifier }, ctx) => {
|
|
828
|
+
const footnoteId = getOrCreateFootnoteId(identifier, ctx.footnoteRegistry || {});
|
|
829
|
+
return new docx.FootnoteReferenceRun(footnoteId);
|
|
751
830
|
};
|
|
752
831
|
|
|
753
832
|
const plugin = function (opts = {}) {
|
|
@@ -755,7 +834,7 @@ const plugin = function (opts = {}) {
|
|
|
755
834
|
this.Compiler = (node) => {
|
|
756
835
|
return mdastToDocx(node, opts, images);
|
|
757
836
|
};
|
|
758
|
-
return (node) =>
|
|
837
|
+
return async (node) => {
|
|
759
838
|
const imageList = [];
|
|
760
839
|
unistUtilVisit.visit(node, "image", (node) => {
|
|
761
840
|
imageList.push(node);
|
|
@@ -765,13 +844,13 @@ const plugin = function (opts = {}) {
|
|
|
765
844
|
}
|
|
766
845
|
const imageResolver = opts.imageResolver;
|
|
767
846
|
invariant(imageResolver, "options.imageResolver is not defined.");
|
|
768
|
-
const imageDatas =
|
|
847
|
+
const imageDatas = await Promise.all(imageList.map(({ url }) => imageResolver(url)));
|
|
769
848
|
images = imageList.reduce((acc, img, i) => {
|
|
770
849
|
acc[img.url] = imageDatas[i];
|
|
771
850
|
return acc;
|
|
772
851
|
}, {});
|
|
773
852
|
return node;
|
|
774
|
-
}
|
|
853
|
+
};
|
|
775
854
|
};
|
|
776
855
|
|
|
777
856
|
module.exports = plugin;
|