inokawa / remark-docx

remark plugin to compile markdown to docx (Microsoft Word, Office Open XML).
https://inokawa.github.io/remark-docx/
MIT License
37 stars 13 forks source link

Table Formatting #43

Open generalleger opened 1 year ago

generalleger commented 1 year ago

Hi - Thanks for making this library. It's great! I have a question. I am trying to format a table where the conversion to docx format occurs on the server (node). I am using remark-gfm to create the table, but it has no style. Is it possible to pass docx table styles into remark-docx?

So far, I think docx doesn't provide the ability to edit, outside of the patcher which only lets you add things. However, I have a table in the middle of the markdown. Also, it doesn't look like docx style options like paragraphStyles, default , or document styles accept options formatting tables. As far as I can tell, it seems like table styles must be passed into docx when the table is created new Table, new Row, and new Cell. So, if I understand everything correctly, if there were to be table styles, they'd have to be applied when the table is created within remark-docx.

Any ideas on how that might work? Thanks!

  const processor = unified()
    .use(remarkParse)
    .use(markdown)
    .use(remarkBreaks)
    .use(remarkGfm)
    .use(docx, { output: 'buffer', styles });
inokawa commented 1 year ago

Hello, I'm not familiar to table options of docx however it looks like that some options are not existed in global as you mentioned. https://docx.js.org/#/usage/tables

I have no time to implement them as an option of remark-docx but any contributions are welcome!

generalleger commented 1 year ago

Thanks for responding so quickly. I'm going to put a little time into seeing if there is a workable solution. Thanks again!!

generalleger commented 1 year ago

Okay, I got something working that will suit my needs in the short term. It's not amazing, but it works! Thankfully your code is easy to read and understand! I wanted to leave the changes here in case this is helpful to anyone else.

I added a tableStyles object to the options. It is structured so that it provides for table options, a header row, and body rows. This is really all I need for now.

That object looks like this and I added it to the DocxOptions interface:

export type TableStyle = {
  options: Omit<ITableOptions, 'rows'>,
  header: {
    row: ITableRowPropertiesOptions,
    cell: ITableCellPropertiesOptions,
    paragraph: IParagraphPropertiesOptions
  },
  body: {
    row: ITableRowPropertiesOptions,
    cell: ITableCellPropertiesOptions,
    paragraph: IParagraphPropertiesOptions
  },
}

It is passed in like so:

  const processor = unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(docx, { output: 'buffer', tableStyle });

That object is then passed into the context.

const mdastToDocx = async (node, { output = 'buffer', title, subject, creator, keywords, description, lastModifiedBy, revision, styles, background, tableStyle }, images) => {

  const { nodes, footnotes } = convertNodes(node.children, {
    deco: {},
    images,
    indent: 0,
    tableStyle
  },);

...

The Table, Row, Cell, and Paragraph constructors then apply the options and styles.

const buildTable = ({ children, align }, ctx) => {
  const cellAligns = align === null || align === void 0 ? void 0 : align.map((a) => {
    switch (a) {
      case 'left':
        return docx.AlignmentType.LEFT;
      case 'right':
        return docx.AlignmentType.RIGHT;
      case 'center':
        return docx.AlignmentType.CENTER;
      default:
        return docx.AlignmentType.LEFT;
    }
  });
  return new docx.Table({
    ...ctx.tableStyle.options,
    rows: children.map((r, i) => {
      const style = i === 0 ? ctx.tableStyle.header : ctx.tableStyle.body;
      return buildTableRow(r, ctx, cellAligns, style);
    }),
  });
};
const buildTableRow = ({ children }, ctx, cellAligns, style) => {
  return new docx.TableRow({
    ...style.row,
    children: children.map((c, i) => {
      return buildTableCell(c, ctx, cellAligns === null || cellAligns === void 0 ? void 0 : cellAligns[i], style);
    }),
  });
};
const buildTableCell = ({ children }, ctx, align, style) => {
  const { nodes } = convertNodes(children, ctx);
  return new docx.TableCell({
    ...style.cell,
    children: [
      new docx.Paragraph({
        ...style.paragraph,
        alignment: align,
        children: nodes,
      }),
    ],
  });
};
PauloJSGG commented 4 months ago

Hi all, I need @generalleger 's code as well. Don't you think a PR is needed? I think it's actually an important addition to the package. Without it the table becomes pretty much unusable.

Thank you

generalleger commented 4 months ago

Hi all, I need @generalleger 's code as well. Don't you think a PR is needed? I think it's actually an important addition to the package. Without it the table becomes pretty much unusable.

Thank you

Paulo - my code is pretty hacky - and I've since modified again to add a title page based on other data internal to my application. So, the code quality is not good enough for a PR. The code mods I described above should work though. If you can't get it to work, let me know and I'll try to post it to my public GH.