Skip to content

Examples

wvbe edited this page Jan 29, 2023 · 19 revisions

All examples on this page can be written in vanilla JS, but for purposes of these examples I found JSX prettier.

Tables

Tables consist of Table, Row and Cell components. Cells can have paragraphs or other tables in them. Assemble like so:

/** @jsx Docx.jsx */
import Docx, { Cell, cm, pt, Paragraph, Row, Table } from 'docxml';

await Docx
  .fromJsx(
    <Table
      columnWidths={[cm(3), cm(5)]}
      borders={{
        bottom: { color: '666666', width: pt(1), type: 'single' },
        left: { color: '666666', width: pt(1), type: 'single' },
        top: { color: '666666', width: pt(1), type: 'single' },
        right: { color: '666666', width: pt(1), type: 'single' },
        insideH: { color: 'CCCCCC', width: pt(1), type: 'dashed' },
        insideV: { color: 'CCCCCC', width: pt(1), type: 'dashed' },
      }}
    >
      <Row>
        <Cell>
          <Paragraph>Cannibal Ox</Paragraph>
        </Cell>
        <Cell>
          <Paragraph>Pigeon</Paragraph>
        </Cell>
      </Row>
      <Row>
        <Cell rowSpan={2}>
          <Paragraph>King Geedorah</Paragraph>
        </Cell>
        <Cell>
          <Paragraph>Lockjaw</Paragraph>
        </Cell>
      </Row>
      <Row>
        <Cell>
          <Paragraph>Anti-Matter</Paragraph>
        </Cell>
      </Row>
    </Table>,
  )
  .toFile('tables.docx');

The Table and Cell component each have props to control borders on them, column widths, or colspans/rowspans. try to make rectangular tables only, meaning that you shouldn't have too few or too many cells on any given row.

Images

Images can be inserted using the Image component. Be sure to always nest them inside Text, a requirement inherited from OOXML. The most important property is data, a UInt8Array of image data. docxml will use whatever width and height you pass in, so make sure they are proportional to the original image dimensions, or at least something you're happy with.

/** @jsx Docx.jsx */
import Docx, { cm, Image, Paragraph, Text } from 'docxml';

await Docx
  .fromJsx(
    <Paragraph>
      <Text>
        <Image
          data={Deno.readFile('test/spacekees.jpeg')}
          width={cm(16)}
          height={cm(16)}
          title="Title"
          alt="Description"
        />
      </Text>
    </Paragraph>,
    )
  .toFile('images.docx');

List numbering

Like OOXML, list numbering is a property on each paragraph that represents a list item. Unlike HTML, there is no tag or component for the list itself.

To use numbering, first ensure that there is a numbering style, and then reference it from a paragraph;

/** @jsx Docx.jsx */
import Docx, { Paragraph, Text, cm } from 'docxml';

const docx = Docx.fromNothing();

const numbering = docx.document.numbering.add({
  type: 'hybridMultilevel',
  levels: [
     { alignment: 'left', format: 'lowerLetter', start: 1, affix: '(%1)' },
     { alignment: 'left', format: 'decimal', start: 1, affix: '%1.', paragraph: { indentation: { left: cm(3) } } },
     // More of these objects for each list level
  ],
});

docx.document.set(
  <Paragraph listItem={{ numbering, depth: 0 }}>
    <Text>This is a list item on the first level</Text>
  </Paragraph>
);

await docx.toFile('list-item.docx')

To reset numbering across lists you must create a whole new abstract numbering, and a new numbering instance, and then reference that from your new list. Counterintuitive as it is, this is what MS Word does too. Reusing abstract numbering across distinct lists is an improvement that docxml could make in the future.

Sections

Sections can be used to determine page size and a few other things. Unlike the actual OOXML structure itself, in docxml sections are components that wrap the section contents. Their use is entirely optional.

/** @jsx Docx.jsx */
import Docx, { cm, Paragraph, Section, Text } from 'docxml';

await Docx
  .fromJsx(
    <Section pageWidth={cm(20)} pageHeight={cm(20)}>
      <Paragraph>
        <Text>This is a square page</Text>
      </Paragraph>
    </Section>,
  )
  .toFile('sections.docx');

Comments

Comments work through registering a comment via the Docx.document.comments instance. A comment has an author, author initials, date, and one Paragraph of content.

/** @jsx Docx.jsx */
import Docx, { Comment, CommentRangeEnd, CommentRangeStart, Paragraph } from 'docxml';

const docx = Docx.fromNothing();

const comment = docx.document.comments.add(
  {
    author: 'Wybe',
    date: new Date(),
    initials: 'X',
  },
  <Paragraph>According to some</Paragraph>,
);

docx.document.set(
  <Paragraph>
    NSYNC is the <CommentRangeStart id={comment} />
    <Comment id={comment} />
    greatest
    <CommentRangeEnd id={comment} /> band in history.
  </Paragraph>,
);

await docx.toFile('comments.docx');

The docx.document.comments.add function then returns a new unique identifier that you can use in any of the components that relate the comment to a specific place in the document. Be careful to use <Comment> and optionally <CommentRangeStart> and <CommentRangeEnd> for every comment exactly once -- more will result in MS Word refusing to open the file.

Headers & footers

Page headers and footers can be inserted by creating instances of them first, and then referencing in a <Section> component.

/** @jsx Docx.jsx */
import Docx, { Paragraph, Section } from '../mod.ts';

const docx = Docx.fromNothing();

const header = docx.document.headers.add(
  'word/header1.xml',
  <Paragraph>SKEET HEADER</Paragraph>
);

const footer = docx.document.footers.add(
  'word/footer1.xml',
  <Paragraph>SKEET FOOTER</Paragraph>
);

docx.document.set(
  <Section headers={header} footers={footer}>
    <Paragraph>This page has a header and a footer</Paragraph>
  </Section>,
);

docx.toFile('headers-footers.docx');

You can set either/both the headers and footers props as string reference to the header relationship, or as an object to specify different headers on the first, even and odd pages.

<Section
  headers={{
    first: headerFirst,
    even: headerEven,
    odd: headerOdd,
  }}
></Section>

When some of these specifications are missing the behavior that your text processor adopts might get a little confusing;. If your headers or footers prop is simply the relationship reference (and not an object of them) then all first/even/odd headers are set to that reference, and the world is simple again.

Hyperlinks & bookmarks

The <Hyperlink> component can be used to create clickable references to external sources (like an URL) or internal sources (like pointing to another section). Hyperlink text is styled by nesting the <Text> component in it.

<Hyperlink url="http://google.com">
  <Text color="blue" isUnderlined>Google</Text>
</Hyperlink>

Hyperlinks to other content in the same document need to point to either one of the preset anchor names (such as "_top") or to a bookmark. Bookmarks should be created through a helper method and placed into the document using <BookmarkRangeStart> and <BookmarkRangeEnd>:

/** @jsx Docx.jsx */

import Docx, { BookmarkRangeEnd, BookmarkRangeStart, Hyperlink, Paragraph, Section, Text } from 'docxml';

const docx = Docx.fromNothing();

const bookmark = docx.bookmarks.create();

docx.document.set([
  <Section pageOrientation={'portrait'}>
    <Paragraph>
      <Hyperlink bookmark={bookmark}>
        <Text>This is a cross-reference to the next section</Text>
      </Hyperlink>
    </Paragraph>
  </Section>,
  <Section pageOrientation={'landscape'}>
    <BookmarkRangeStart bookmark={bookmark} />
    <Paragraph>
      <Hyperlink url="https://github.com/wvbe/docxml">
        <Text>This is a hyperlink to external target "github.com/wvbe/docxml"</Text>
      </Hyperlink>
    </Paragraph>
    <BookmarkRangeEnd bookmark={bookmark} />
  </Section>,
]);

docx.toFile('hyperlinks.docx');
Clone this wiki locally