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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # remark-docx
2
2
 
3
- ![npm](https://img.shields.io/npm/v/remark-docx) ![check](https://github.com/inokawa/remark-docx/workflows/check/badge.svg) ![demo](https://github.com/inokawa/remark-docx/workflows/demo/badge.svg)
3
+ ![npm](https://img.shields.io/npm/v/remark-docx) ![npm](https://img.shields.io/npm/dw/remark-docx) ![check](https://github.com/inokawa/remark-docx/workflows/check/badge.svg) ![demo](https://github.com/inokawa/remark-docx/workflows/demo/badge.svg)
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
- - [ ] definition
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
- - [ ] linkReference
37
- - [ ] imageReference
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. Clone this repo.
103
+ 1. Fork this repo.
104
104
  2. Run `npm install`.
105
105
  3. Commit your fix.
106
- 4. Make a PR and confirm all the CI checks passed.
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 { nodes, footnotes } = convertNodes(node.children, {
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
- case "heading":
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
- case "list":
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
- // FIXME: unimplemented
546
+ // noop
502
547
  break;
503
- case "footnoteDefinition":
504
- footnotes[node.identifier] = buildFootnoteDefinition(node, ctx);
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 { nodes } = convertNodes(children, {
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
- // FIXME: unimplemented
581
+ results.push(...buildLinkReference(node, ctx));
535
582
  break;
536
- case "imageReference":
537
- // FIXME: unimplemented
583
+ case "imageReference": {
584
+ const image = buildImageReference(node, ctx);
585
+ if (image) {
586
+ results.push(image);
587
+ }
538
588
  break;
539
- case "footnote":
540
- results.push(buildFootnote(node, ctx));
589
+ }
590
+ case "footnote": {
591
+ // inline footnote was removed in mdast v5
541
592
  break;
593
+ }
542
594
  case "footnoteReference":
543
- // do we need context here?
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 { nodes } = convertNodes(children, ctx);
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 heading;
643
+ let headingLevel;
596
644
  switch (depth) {
597
645
  case 1:
598
- heading = docx.HeadingLevel.TITLE;
646
+ headingLevel = docx.HeadingLevel.TITLE;
599
647
  break;
600
648
  case 2:
601
- heading = docx.HeadingLevel.HEADING_1;
649
+ headingLevel = docx.HeadingLevel.HEADING_1;
602
650
  break;
603
651
  case 3:
604
- heading = docx.HeadingLevel.HEADING_2;
652
+ headingLevel = docx.HeadingLevel.HEADING_2;
605
653
  break;
606
654
  case 4:
607
- heading = docx.HeadingLevel.HEADING_3;
655
+ headingLevel = docx.HeadingLevel.HEADING_3;
608
656
  break;
609
657
  case 5:
610
- heading = docx.HeadingLevel.HEADING_4;
658
+ headingLevel = docx.HeadingLevel.HEADING_4;
611
659
  break;
612
660
  case 6:
613
- heading = docx.HeadingLevel.HEADING_5;
661
+ headingLevel = docx.HeadingLevel.HEADING_5;
614
662
  break;
615
663
  }
616
- const { nodes } = convertNodes(children, ctx);
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
- const { nodes } = convertNodes(children, { ...ctx, indent: ctx.indent + 1 });
629
- return nodes;
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
- const { nodes } = convertNodes(children, {
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 { nodes } = convertNodes(children, ctx);
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, title: _title }, ctx) => {
725
- const { nodes } = convertNodes(children, ctx);
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, title: _title, alt: _alt }, images) => {
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 buildFootnote = ({ children }, ctx) => {
744
- // FIXME: transform to paragraph for now
745
- const { nodes } = convertNodes(children, ctx);
746
- return new docx.Paragraph({
747
- children: nodes,
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 buildFootnoteDefinition = ({ children }, ctx) => {
751
- return {
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
- const { nodes } = convertNodes([node], ctx);
754
- return nodes[0];
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
- // do we need Context?
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 imageDatas = await Promise.all(imageList.map(({ url }) => imageResolver(url)));
779
- images = imageList.reduce((acc, img, i) => {
780
- acc[img.url] = imageDatas[i];
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;