remark-docx 0.1.6 → 0.1.8
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 +7 -6
- package/lib/index.js +139 -58
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +140 -59
- package/lib/index.mjs.map +1 -1
- package/lib/latex.d.ts +1 -2
- package/lib/{transformer.d.ts → mdast-to-docx.d.ts} +1 -13
- package/lib/plugin.d.ts +1 -1
- package/lib/utils.d.ts +1 -2
- package/package.json +32 -31
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
|
|
|
@@ -23,7 +23,7 @@ If you have some feature requests or improvements, please create a [issue](https
|
|
|
23
23
|
- [ ] code
|
|
24
24
|
- [ ] yaml
|
|
25
25
|
- [ ] toml
|
|
26
|
-
- [
|
|
26
|
+
- [x] definition
|
|
27
27
|
- [x] footnoteDefinition
|
|
28
28
|
- [x] text
|
|
29
29
|
- [x] emphasis
|
|
@@ -33,8 +33,8 @@ If you have some feature requests or improvements, please create a [issue](https
|
|
|
33
33
|
- [x] break
|
|
34
34
|
- [x] link
|
|
35
35
|
- [x] image
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
36
|
+
- [x] linkReference
|
|
37
|
+
- [x] imageReference
|
|
38
38
|
- [x] footnote
|
|
39
39
|
- [x] footnoteReference
|
|
40
40
|
- [x] LaTeX support with math and inlineMath ([remark-math](https://github.com/remarkjs/remark-math) is required)
|
|
@@ -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
|
@@ -4,9 +4,15 @@ var unistUtilVisit = require('unist-util-visit');
|
|
|
4
4
|
var docx = require('docx');
|
|
5
5
|
var unifiedLatexUtilParse = require('@unified-latex/unified-latex-util-parse');
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
7
10
|
const unreachable = (_) => {
|
|
8
11
|
throw new Error("unreachable");
|
|
9
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
10
16
|
function invariant(cond, message) {
|
|
11
17
|
if (!cond)
|
|
12
18
|
throw new Error(message);
|
|
@@ -336,6 +342,9 @@ const mapNode = (n, runs) => {
|
|
|
336
342
|
}
|
|
337
343
|
return [];
|
|
338
344
|
};
|
|
345
|
+
/**
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
339
348
|
const parseLatex = (value) => {
|
|
340
349
|
const parsed = unifiedLatexUtilParse.parseMath(value);
|
|
341
350
|
const paragraphs = [[]];
|
|
@@ -418,11 +427,44 @@ const DEFAULT_NUMBERINGS = [
|
|
|
418
427
|
},
|
|
419
428
|
},
|
|
420
429
|
];
|
|
430
|
+
const createFootnoteRegistry = () => {
|
|
431
|
+
const idToInternalId = new Map();
|
|
432
|
+
const defs = new Map();
|
|
433
|
+
const getId = (id) => {
|
|
434
|
+
let internalId = idToInternalId.get(id);
|
|
435
|
+
if (internalId == null) {
|
|
436
|
+
idToInternalId.set(id, (internalId = idToInternalId.size + 1));
|
|
437
|
+
}
|
|
438
|
+
return internalId;
|
|
439
|
+
};
|
|
440
|
+
return {
|
|
441
|
+
ref: (id) => {
|
|
442
|
+
return getId(id);
|
|
443
|
+
},
|
|
444
|
+
def: (id, def) => {
|
|
445
|
+
const internalId = getId(id);
|
|
446
|
+
defs.set(internalId, def);
|
|
447
|
+
},
|
|
448
|
+
footnotes: () => {
|
|
449
|
+
return defs.entries().reduce((acc, [key, def]) => {
|
|
450
|
+
acc[key] = def;
|
|
451
|
+
return acc;
|
|
452
|
+
}, {});
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
};
|
|
421
456
|
const mdastToDocx = async (node, { output = "buffer", title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, }, images) => {
|
|
422
|
-
const
|
|
457
|
+
const definition = {};
|
|
458
|
+
unistUtilVisit.visit(node, "definition", (node) => {
|
|
459
|
+
definition[node.identifier] = node.url;
|
|
460
|
+
});
|
|
461
|
+
const footnote = createFootnoteRegistry();
|
|
462
|
+
const nodes = convertNodes(node.children, {
|
|
423
463
|
deco: {},
|
|
424
464
|
images,
|
|
425
465
|
indent: 0,
|
|
466
|
+
def: definition,
|
|
467
|
+
footnote,
|
|
426
468
|
});
|
|
427
469
|
const doc = new docx.Document({
|
|
428
470
|
title,
|
|
@@ -434,7 +476,7 @@ const mdastToDocx = async (node, { output = "buffer", title, subject, creator, k
|
|
|
434
476
|
revision,
|
|
435
477
|
styles,
|
|
436
478
|
background,
|
|
437
|
-
footnotes,
|
|
479
|
+
footnotes: footnote.footnotes(),
|
|
438
480
|
sections: [{ children: nodes }],
|
|
439
481
|
numbering: {
|
|
440
482
|
config: [
|
|
@@ -458,24 +500,27 @@ const mdastToDocx = async (node, { output = "buffer", title, subject, creator, k
|
|
|
458
500
|
};
|
|
459
501
|
const convertNodes = (nodes, ctx) => {
|
|
460
502
|
const results = [];
|
|
461
|
-
let footnotes = {};
|
|
462
503
|
for (const node of nodes) {
|
|
463
504
|
switch (node.type) {
|
|
464
|
-
case "paragraph":
|
|
505
|
+
case "paragraph": {
|
|
465
506
|
results.push(buildParagraph(node, ctx));
|
|
466
507
|
break;
|
|
467
|
-
|
|
508
|
+
}
|
|
509
|
+
case "heading": {
|
|
468
510
|
results.push(buildHeading(node, ctx));
|
|
469
511
|
break;
|
|
512
|
+
}
|
|
470
513
|
case "thematicBreak":
|
|
471
514
|
results.push(buildThematicBreak());
|
|
472
515
|
break;
|
|
473
|
-
case "blockquote":
|
|
516
|
+
case "blockquote": {
|
|
474
517
|
results.push(...buildBlockquote(node, ctx));
|
|
475
518
|
break;
|
|
476
|
-
|
|
519
|
+
}
|
|
520
|
+
case "list": {
|
|
477
521
|
results.push(...buildList(node, ctx));
|
|
478
522
|
break;
|
|
523
|
+
}
|
|
479
524
|
case "listItem":
|
|
480
525
|
invariant(false, "unreachable");
|
|
481
526
|
case "table":
|
|
@@ -498,11 +543,12 @@ const convertNodes = (nodes, ctx) => {
|
|
|
498
543
|
// FIXME: unimplemented
|
|
499
544
|
break;
|
|
500
545
|
case "definition":
|
|
501
|
-
//
|
|
546
|
+
// noop
|
|
502
547
|
break;
|
|
503
|
-
case "footnoteDefinition":
|
|
504
|
-
|
|
548
|
+
case "footnoteDefinition": {
|
|
549
|
+
registerFootnoteDefinition(node, ctx);
|
|
505
550
|
break;
|
|
551
|
+
}
|
|
506
552
|
case "text":
|
|
507
553
|
results.push(buildText(node.value, ctx.deco));
|
|
508
554
|
break;
|
|
@@ -510,7 +556,7 @@ const convertNodes = (nodes, ctx) => {
|
|
|
510
556
|
case "strong":
|
|
511
557
|
case "delete": {
|
|
512
558
|
const { type, children } = node;
|
|
513
|
-
const
|
|
559
|
+
const nodes = convertNodes(children, {
|
|
514
560
|
...ctx,
|
|
515
561
|
deco: { ...ctx.deco, [type]: true },
|
|
516
562
|
});
|
|
@@ -524,24 +570,29 @@ const convertNodes = (nodes, ctx) => {
|
|
|
524
570
|
case "break":
|
|
525
571
|
results.push(buildBreak());
|
|
526
572
|
break;
|
|
527
|
-
case "link":
|
|
573
|
+
case "link": {
|
|
528
574
|
results.push(buildLink(node, ctx));
|
|
529
575
|
break;
|
|
576
|
+
}
|
|
530
577
|
case "image":
|
|
531
578
|
results.push(buildImage(node, ctx.images));
|
|
532
579
|
break;
|
|
533
580
|
case "linkReference":
|
|
534
|
-
|
|
581
|
+
results.push(...buildLinkReference(node, ctx));
|
|
535
582
|
break;
|
|
536
|
-
case "imageReference":
|
|
537
|
-
|
|
583
|
+
case "imageReference": {
|
|
584
|
+
const image = buildImageReference(node, ctx);
|
|
585
|
+
if (image) {
|
|
586
|
+
results.push(image);
|
|
587
|
+
}
|
|
538
588
|
break;
|
|
539
|
-
|
|
540
|
-
|
|
589
|
+
}
|
|
590
|
+
case "footnote": {
|
|
591
|
+
// inline footnote was removed in mdast v5
|
|
541
592
|
break;
|
|
593
|
+
}
|
|
542
594
|
case "footnoteReference":
|
|
543
|
-
|
|
544
|
-
results.push(buildFootnoteReference(node));
|
|
595
|
+
results.push(buildFootnoteReference(node, ctx));
|
|
545
596
|
break;
|
|
546
597
|
case "math":
|
|
547
598
|
results.push(...buildMath(node));
|
|
@@ -554,14 +605,11 @@ const convertNodes = (nodes, ctx) => {
|
|
|
554
605
|
break;
|
|
555
606
|
}
|
|
556
607
|
}
|
|
557
|
-
return
|
|
558
|
-
nodes: results,
|
|
559
|
-
footnotes,
|
|
560
|
-
};
|
|
608
|
+
return results;
|
|
561
609
|
};
|
|
562
610
|
const buildParagraph = ({ children }, ctx) => {
|
|
563
611
|
const list = ctx.list;
|
|
564
|
-
const
|
|
612
|
+
const nodes = convertNodes(children, ctx);
|
|
565
613
|
if (list && list.checked != null) {
|
|
566
614
|
nodes.unshift(new docx.CheckBox({
|
|
567
615
|
checked: list.checked,
|
|
@@ -592,30 +640,30 @@ const buildParagraph = ({ children }, ctx) => {
|
|
|
592
640
|
});
|
|
593
641
|
};
|
|
594
642
|
const buildHeading = ({ children, depth }, ctx) => {
|
|
595
|
-
let
|
|
643
|
+
let headingLevel;
|
|
596
644
|
switch (depth) {
|
|
597
645
|
case 1:
|
|
598
|
-
|
|
646
|
+
headingLevel = docx.HeadingLevel.TITLE;
|
|
599
647
|
break;
|
|
600
648
|
case 2:
|
|
601
|
-
|
|
649
|
+
headingLevel = docx.HeadingLevel.HEADING_1;
|
|
602
650
|
break;
|
|
603
651
|
case 3:
|
|
604
|
-
|
|
652
|
+
headingLevel = docx.HeadingLevel.HEADING_2;
|
|
605
653
|
break;
|
|
606
654
|
case 4:
|
|
607
|
-
|
|
655
|
+
headingLevel = docx.HeadingLevel.HEADING_3;
|
|
608
656
|
break;
|
|
609
657
|
case 5:
|
|
610
|
-
|
|
658
|
+
headingLevel = docx.HeadingLevel.HEADING_4;
|
|
611
659
|
break;
|
|
612
660
|
case 6:
|
|
613
|
-
|
|
661
|
+
headingLevel = docx.HeadingLevel.HEADING_5;
|
|
614
662
|
break;
|
|
615
663
|
}
|
|
616
|
-
const
|
|
664
|
+
const nodes = convertNodes(children, ctx);
|
|
617
665
|
return new docx.Paragraph({
|
|
618
|
-
heading,
|
|
666
|
+
heading: headingLevel,
|
|
619
667
|
children: nodes,
|
|
620
668
|
});
|
|
621
669
|
};
|
|
@@ -625,8 +673,10 @@ const buildThematicBreak = (_) => {
|
|
|
625
673
|
});
|
|
626
674
|
};
|
|
627
675
|
const buildBlockquote = ({ children }, ctx) => {
|
|
628
|
-
|
|
629
|
-
|
|
676
|
+
return convertNodes(children, {
|
|
677
|
+
...ctx,
|
|
678
|
+
indent: ctx.indent + 1,
|
|
679
|
+
});
|
|
630
680
|
};
|
|
631
681
|
const buildList = ({ children, ordered, start: _start, spread: _spread }, ctx) => {
|
|
632
682
|
const list = {
|
|
@@ -641,11 +691,10 @@ const buildList = ({ children, ordered, start: _start, spread: _spread }, ctx) =
|
|
|
641
691
|
});
|
|
642
692
|
};
|
|
643
693
|
const buildListItem = ({ children, checked, spread: _spread }, ctx) => {
|
|
644
|
-
|
|
694
|
+
return convertNodes(children, {
|
|
645
695
|
...ctx,
|
|
646
696
|
...(ctx.list && { list: { ...ctx.list, checked: checked !== null && checked !== void 0 ? checked : undefined } }),
|
|
647
697
|
});
|
|
648
|
-
return nodes;
|
|
649
698
|
};
|
|
650
699
|
const buildTable = ({ children, align }, ctx) => {
|
|
651
700
|
const cellAligns = align === null || align === void 0 ? void 0 : align.map((a) => {
|
|
@@ -674,7 +723,7 @@ const buildTableRow = ({ children }, ctx, cellAligns) => {
|
|
|
674
723
|
});
|
|
675
724
|
};
|
|
676
725
|
const buildTableCell = ({ children }, ctx, align) => {
|
|
677
|
-
const
|
|
726
|
+
const nodes = convertNodes(children, ctx);
|
|
678
727
|
return new docx.TableCell({
|
|
679
728
|
children: [
|
|
680
729
|
new docx.Paragraph({
|
|
@@ -690,7 +739,7 @@ const buildHtml = ({ value }) => {
|
|
|
690
739
|
children: [buildText(value, {})],
|
|
691
740
|
});
|
|
692
741
|
};
|
|
693
|
-
const buildCode = ({ value, lang: _lang, meta: _meta }) => {
|
|
742
|
+
const buildCode = ({ value, lang: _lang, meta: _meta, }) => {
|
|
694
743
|
// FIXME: transform to text for now
|
|
695
744
|
return new docx.Paragraph({
|
|
696
745
|
children: [buildText(value, {})],
|
|
@@ -721,14 +770,14 @@ const buildText = (text, deco) => {
|
|
|
721
770
|
const buildBreak = (_) => {
|
|
722
771
|
return new docx.TextRun({ text: "", break: 1 });
|
|
723
772
|
};
|
|
724
|
-
const buildLink = ({ children, url
|
|
725
|
-
const
|
|
773
|
+
const buildLink = ({ children, url }, ctx) => {
|
|
774
|
+
const nodes = convertNodes(children, ctx);
|
|
726
775
|
return new docx.ExternalHyperlink({
|
|
727
776
|
link: url,
|
|
728
777
|
children: nodes,
|
|
729
778
|
});
|
|
730
779
|
};
|
|
731
|
-
const buildImage = ({ url
|
|
780
|
+
const buildImage = ({ url }, images) => {
|
|
732
781
|
const img = images[url];
|
|
733
782
|
invariant(img, `Fetch image was failed: ${url}`);
|
|
734
783
|
const { image, width, height } = img;
|
|
@@ -740,24 +789,36 @@ const buildImage = ({ url, title: _title, alt: _alt }, images) => {
|
|
|
740
789
|
},
|
|
741
790
|
});
|
|
742
791
|
};
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
});
|
|
792
|
+
const buildLinkReference = ({ children, identifier }, ctx) => {
|
|
793
|
+
const def = ctx.def[identifier];
|
|
794
|
+
if (def == null) {
|
|
795
|
+
return convertNodes(children, ctx);
|
|
796
|
+
}
|
|
797
|
+
return [buildLink({ children, url: def }, ctx)];
|
|
749
798
|
};
|
|
750
|
-
const
|
|
751
|
-
|
|
799
|
+
const buildImageReference = ({ identifier }, ctx) => {
|
|
800
|
+
const def = ctx.def[identifier];
|
|
801
|
+
if (def == null) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
return buildImage({ url: def }, ctx.images);
|
|
805
|
+
};
|
|
806
|
+
const registerFootnoteDefinition = ({ children, identifier }, ctx) => {
|
|
807
|
+
const definition = {
|
|
752
808
|
children: children.map((node) => {
|
|
753
|
-
|
|
754
|
-
|
|
809
|
+
// Convert each node and extract the first result as a paragraph
|
|
810
|
+
const nodes = convertNodes([node], ctx);
|
|
811
|
+
if (nodes[0] instanceof docx.Paragraph) {
|
|
812
|
+
return nodes[0];
|
|
813
|
+
}
|
|
814
|
+
// For non-paragraph content, wrap in a paragraph
|
|
815
|
+
return new docx.Paragraph({ children: nodes });
|
|
755
816
|
}),
|
|
756
817
|
};
|
|
818
|
+
ctx.footnote.def(identifier, definition);
|
|
757
819
|
};
|
|
758
|
-
const buildFootnoteReference = ({ identifier }) => {
|
|
759
|
-
|
|
760
|
-
return new docx.FootnoteReferenceRun(parseInt(identifier));
|
|
820
|
+
const buildFootnoteReference = ({ identifier }, ctx) => {
|
|
821
|
+
return new docx.FootnoteReferenceRun(ctx.footnote.ref(identifier));
|
|
761
822
|
};
|
|
762
823
|
|
|
763
824
|
const plugin = function (opts = {}) {
|
|
@@ -770,14 +831,34 @@ const plugin = function (opts = {}) {
|
|
|
770
831
|
unistUtilVisit.visit(node, "image", (node) => {
|
|
771
832
|
imageList.push(node);
|
|
772
833
|
});
|
|
834
|
+
const defs = new Map();
|
|
835
|
+
unistUtilVisit.visit(node, "definition", (node) => {
|
|
836
|
+
defs.set(node.identifier, node);
|
|
837
|
+
});
|
|
838
|
+
unistUtilVisit.visit(node, "imageReference", (node) => {
|
|
839
|
+
const maybeImage = defs.get(node.identifier);
|
|
840
|
+
if (maybeImage) {
|
|
841
|
+
imageList.push(maybeImage);
|
|
842
|
+
}
|
|
843
|
+
});
|
|
773
844
|
if (imageList.length === 0) {
|
|
774
845
|
return node;
|
|
775
846
|
}
|
|
776
847
|
const imageResolver = opts.imageResolver;
|
|
777
848
|
invariant(imageResolver, "options.imageResolver is not defined.");
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
849
|
+
const resolved = new Set();
|
|
850
|
+
const promises = [];
|
|
851
|
+
imageList.forEach(({ url }) => {
|
|
852
|
+
if (!resolved.has(url)) {
|
|
853
|
+
resolved.add(url);
|
|
854
|
+
promises.push((async () => {
|
|
855
|
+
const img = await imageResolver(url);
|
|
856
|
+
return { img, url };
|
|
857
|
+
})());
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
images = (await Promise.all(promises)).reduce((acc, { img, url }) => {
|
|
861
|
+
acc[url] = img;
|
|
781
862
|
return acc;
|
|
782
863
|
}, {});
|
|
783
864
|
return node;
|