remark-docx icon indicating copy to clipboard operation
remark-docx copied to clipboard

Table Formatting

Open generalleger opened this issue 1 year ago • 5 comments

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 });

generalleger avatar Oct 25 '23 02:10 generalleger

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!

inokawa avatar Oct 25 '23 04:10 inokawa

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

generalleger avatar Oct 25 '23 12:10 generalleger

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,
      }),
    ],
  });
};

generalleger avatar Oct 25 '23 14:10 generalleger

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

PauloJSGG avatar Jun 26 '24 22:06 PauloJSGG

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.

generalleger avatar Jun 27 '24 15:06 generalleger