DEV Community

Serge Artishev
Serge Artishev

Posted on

Building a DOCX to Markdown Converter with Node.js

Welcome to a step-by-step guide on building a powerful DOCX to Markdown converter using Node.js. This project is a great way to learn about file manipulation, command-line interfaces, and converting document formats. By the end of this series, you'll have a tool that not only converts DOCX files to Markdown but also extracts images and formats tables. Let's dive in!

Table of Contents

  1. Introduction
  2. Setting Up the Project
  3. Basic DOCX to HTML Conversion
  4. Converting HTML to Markdown
  5. Extracting Images
  6. Formatting Tables
  7. Conclusion

Introduction

Markdown is a lightweight markup language with plain text formatting syntax. It's widely used for documentation due to its simplicity and readability. However, many documents are created in DOCX format, especially in corporate environments. Converting these documents to Markdown can be tedious if done manually. This is where our converter comes in handy.

Setting Up the Project

First, let's create a new directory for our project and initialize it with npm.

mkdir docx-to-md-converter
cd docx-to-md-converter
npm init -y
Enter fullscreen mode Exit fullscreen mode

Next, we'll install the necessary dependencies. We'll use mammoth for converting DOCX to HTML, turndown for converting HTML to Markdown, commander for building the CLI, and uuid for unique image names.

npm install mammoth turndown commander uuid
Enter fullscreen mode Exit fullscreen mode

Create a new file named index.js in your project directory. This will be the main file for our converter.

touch index.js
Enter fullscreen mode Exit fullscreen mode

Basic DOCX to HTML Conversion

Let's start by writing a simple script to convert DOCX files to HTML. We'll use the mammoth library for this.

Open index.js and add the following code:

#!/usr/bin/env node

import * as fs from 'fs';
import * as path from 'path';
import * as mammoth from 'mammoth';
import { program } from 'commander';

program
  .version('1.0.0')
  .description('Convert DOCX to HTML')
  .argument('<input>', 'Input DOCX file')
  .argument('[output]', 'Output HTML file (default: same as input with .html extension)')
  .action(async (input, output) => {
    try {
      await convertDocxToHtml(input, output);
    } catch (error) {
      console.error('Error:', error);
      process.exit(1);
    }
  });

program.parse(process.argv);

async function convertDocxToHtml(inputFile, outputFile) {
  if (!outputFile) {
    outputFile = path.join(path.dirname(inputFile), `${path.basename(inputFile, '.docx')}.html`);
  }

  const result = await mammoth.convertToHtml({ path: inputFile });
  await fs.promises.writeFile(outputFile, result.value);
  console.log(`Conversion complete. Output saved to ${outputFile}`);
}
Enter fullscreen mode Exit fullscreen mode

This script uses commander to parse command-line arguments, mammoth to convert DOCX to HTML, and fs to write the output to a file. To make this script executable, add the following line at the top of index.js:

#!/usr/bin/env node
Enter fullscreen mode Exit fullscreen mode

Make sure the script has execute permissions:

chmod +x index.js
Enter fullscreen mode Exit fullscreen mode

Now you can run the script to convert a DOCX file to HTML:

node index.js example.docx example.html
Enter fullscreen mode Exit fullscreen mode

Converting HTML to Markdown

Next, we'll add the functionality to convert HTML to Markdown using turndown.

First, install turndown:

npm install turndown
Enter fullscreen mode Exit fullscreen mode

Update index.js to include the HTML to Markdown conversion:

#!/usr/bin/env node

import * as fs from 'fs';
import * as path from 'path';
import * as mammoth from 'mammoth';
import TurndownService from 'turndown';
import { program } from 'commander';

program
  .version('1.0.0')
  .description('Convert DOCX to Markdown')
  .argument('<input>', 'Input DOCX file')
  .argument('[output]', 'Output Markdown file (default: same as input with .md extension)')
  .action(async (input, output) => {
    try {
      await convertDocxToMarkdown(input, output);
    } catch (error) {
      console.error('Error:', error);
      process.exit(1);
    }
  });

program.parse(process.argv);

async function convertDocxToMarkdown(inputFile, outputFile) {
  if (!outputFile) {
    outputFile = path.join(path.dirname(inputFile), `${path.basename(inputFile, '.docx')}.md`);
  }

  const result = await mammoth.convertToHtml({ path: inputFile });
  const turndownService = new TurndownService();
  const markdown = turndownService.turndown(result.value);

  await fs.promises.writeFile(outputFile, markdown);
  console.log(`Conversion complete. Output saved to ${outputFile}`);
}
Enter fullscreen mode Exit fullscreen mode

Now you can convert DOCX files to Markdown:

node index.js example.docx example.md
Enter fullscreen mode Exit fullscreen mode

Extracting Images

DOCX files often contain images that we need to handle. We'll extract these images and save them to a folder, updating the image links in the Markdown file.

Update index.js to include image extraction:

#!/usr/bin/env node

import * as fs from 'fs';
import * as path from 'path';
import * as mammoth from 'mammoth';
import TurndownService from 'turndown';
import { program } from 'commander';
import { v4 as uuidv4 } from 'uuid';

program
  .version('1.0.0')
  .description('Convert DOCX to Markdown with image extraction')
  .argument('<input>', 'Input DOCX file')
  .argument('[output]', 'Output Markdown file (default: same as input with .md extension)')
  .action(async (input, output) => {
    try {
      await convertDocxToMarkdown(input, output);
    } catch (error) {
      console.error('Error:', error);
      process.exit(1);
    }
  });

program.parse(process.argv);

async function convertDocxToMarkdown(inputFile, outputFile) {
  if (!outputFile) {
    outputFile = path.join(path.dirname(inputFile), `${path.basename(inputFile, '.docx')}.md`);
  }

  const imageDir = path.join(path.dirname(outputFile), 'images');
  if (!fs.existsSync(imageDir)) {
    fs.mkdirSync(imageDir, { recursive: true });
  }

  const result = await mammoth.convertToHtml({ path: inputFile }, {
    convertImage: mammoth.images.imgElement(async (image) => {
      const buffer = await image.read();
      const extension = image.contentType.split('/')[1];
      const imageName = `image-${uuidv4()}.${extension}`;
      const imagePath = path.join(imageDir, imageName);
      await fs.promises.writeFile(imagePath, buffer);
      return { src: `images/${imageName}` };
    })
  });

  const turndownService = new TurndownService();
  const markdown = turndownService.turndown(result.value);

  await fs.promises.writeFile(outputFile, markdown);
  console.log(`Conversion complete. Output saved to ${outputFile}`);
}
Enter fullscreen mode Exit fullscreen mode

Now, images will be extracted and saved in an images folder, and the Markdown file will contain the correct links to these images.

Formatting Tables

The final feature we'll add is table formatting. DOCX files often contain tables that need to be correctly formatted in Markdown.

Update index.js to include table formatting:

#!/usr/bin/env node

import * as fs from 'fs';
import * as path from 'path';
import * as mammoth from 'mammoth';
import TurndownService from 'turndown';
import { program } from 'commander';
import { v4 as uuidv4 } from 'uuid';

program
  .version('1.0.0')
  .description('Convert DOCX to Markdown with image extraction and table formatting')
  .argument('<input>', 'Input DOCX file')
  .argument('[output]', 'Output Markdown file (default: same as input with .md extension)')
  .action(async (input, output) => {
    try {
      await convertDocxToMarkdown(input, output);
    } catch (error) {
      console.error('Error:', error);
      process.exit(1);
    }
  });

program.parse(process.argv);

function createMarkdownTable(table) {
  const rows = Array.from(table.rows);
  if (rows.length === 0) return '';

  const headers = Array.from(rows[0].cells).map(cell => cell.textContent?.trim() || '');
  const markdownRows = rows.slice(1).map(row => 
    Array.from(row.cells).map(cell => cell.textContent?.trim() || '')
  );

  let markdown = '| ' + headers.join(' | ') + ' |\n';
  markdown += '| ' + headers.map(() => '---').join(' | ') + ' |\n';
  markdownRows.forEach(row => {
    markdown += '| ' + row.join(' | ') + ' |\n

';
  });

  return markdown;
}

async function convertDocxToMarkdown(inputFile, outputFile) {
  if (!outputFile) {
    outputFile = path.join(path.dirname(inputFile), `${path.basename(inputFile, '.docx')}.md`);
  }

  const imageDir = path.join(path.dirname(outputFile), 'images');
  if (!fs.existsSync(imageDir)) {
    fs.mkdirSync(imageDir, { recursive: true });
  }

  const result = await mammoth.convertToHtml({ path: inputFile }, {
    convertImage: mammoth.images.imgElement(async (image) => {
      const buffer = await image.read();
      const extension = image.contentType.split('/')[1];
      const imageName = `image-${uuidv4()}.${extension}`;
      const imagePath = path.join(imageDir, imageName);
      await fs.promises.writeFile(imagePath, buffer);
      return { src: `images/${imageName}` };
    })
  });

  let html = result.value;

  const turndownService = new TurndownService();

  turndownService.addRule('table', {
    filter: 'table',
    replacement: function(content, node) {
      return '\n\n' + createMarkdownTable(node) + '\n\n';
    }
  });

  const markdown = turndownService.turndown(html);

  await fs.promises.writeFile(outputFile, markdown);
  console.log(`Conversion complete. Output saved to ${outputFile}`);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this blog post, we built a DOCX to Markdown converter step by step, adding features like image extraction and table formatting. This tool demonstrates the power and flexibility of Node.js for handling file manipulations and conversions.

The source code for this project is available on GitHub, where you can find the latest updates, contribute to the project, and explore further enhancements.

Thank you for following along with this guide. Happy coding!

Top comments (1)

Collapse
 
darren_cooper_bb51950eca1 profile image
Darren Cooper

Thanks for this - I have been thinking about this very task. Incredibly useful

Some comments have been hidden by the post's author - find out more