Q:

Converting file from Markdown-like markup into HTML using repeated substitutions

The following code reads a file, splits its data, replaces some characters in the data, and then joins the data again (I added more details in the comments):
// read plain text file and make content available in data
fs.readFile(filename, 'utf8', function(err, data) {
  if (err) throw err

  // turn the data into an array
  data = data.split('\n\n')

  // make a clone of the array to be used in the if statements. 
  var tree = data.slice()

  for (var i = 0; i < tree.length; ++i) {
    // turn #s into heading tags if #s are present
    if (tree[i].match(/^#/g)) {
      data[i] = data[i]
        .replace(/^#### (.*)/gm, '<h4>$1</h4>')
        .replace(/^### (.*)/gm, '<h3>$1</h3>')
        .replace(/^## (.*)/gm, '<h2>$1</h2>')
        .replace(/^# (.*)/gm, '<h1>$1</h1>')
    }

    // smarten " or ' if present
    if (tree[i].match(/"|'/g)) {
      data[i] = data[i]
        .replace(/"(?=\b|\*|')/g, '“')
        .replace(/"(?!\b|\*|')/g, '”')
        .replace(/'(?!\b|\*)|(?=\b)'(?=\b)/g, '’')
        .replace(/'(?=\b|\*)/g, '‘')
    }

    // turn -- into — if present
    if (tree[i].match(/--/g)) {
      data[i] = data[i]
        .replace(/\b--(\b)*/g, '—')
    }

    // turn * or ** into italics and bold if present
    if (tree[i].match(/\*\*|\*/g)) {
      data[i] = data[i]
        .replace(/\*\*([^\*|\s]+)\*\*/g, '<strong>$1</strong>')
        .replace(/\*([^\*|\s]+)\*/g, '<em>$1</em>')
    }

    // surround every element with p tags if the 
    // element doesn't start with an #.  Also if the previous element of
    // the element is # or * * * add the p tag with the class ni 
    if (tree[i].match(/^[^#]/g)) {
      if (tree[i - 1] && (tree[i - 1].match(/^#/g) || tree[i - 1] === "* * *")) {
        data[i] = '<p class="ni">' + data[i] + '</p>'
      } else {
        data[i] = '<p>' + data[i] + '</p>'
      }
    }
  }

  // lastly, put the array together again to the saved as HTML
  data = data.join('\n\n')

  saveHtml(data)
})

Example input:
# Title

'Single quotes'

"Double Quotes"

* * *

Paragraphs

Output:
<h1>Title</h1>

<p class="ni">‘Single quotes’</p>

<p>“Double Quotes”</p>

<p>* * *</p>

<p class="ni">Paragraphs
</p>

Is there a cleaner way to write those if statements?  Or at least create a function so that there is less code in that fs.readFile block? 

A:

Here's what I would do (note: in ES6, though it is trivial to convert back to ES5).  It doesn't match your code perfectly, but it should get get the point across.  Essentially I extract everything out into smaller methods and then have a method that can perform batch replacements.  It's not shorter nor really any less complex, but it is (to me) easier to read and follow. 
Notes:

I'd normally comment this like crazy so that I could remember what the regexps do, but in this example I haven't.  Almost all of them are your regexp's, and the ones that are different aren't so different that they won't be obvious to you. 
I prefer HTML Entities for things like double quotes and dashes.  
I'm not worrying about the p class="ni" stuff, but it would be trivial to add. 

ES6 Code:
function massReplace(text, replacementArray) {
  let results = text;
  for (let [regex, replacement] of replacementArray) {
    results = results.replace(regex, replacement);
  }
  return results;
}

function transformHeadings(text, orig) {
  if (orig.match(/^#{1,6}\s/)) {
    return massReplace(text,
                       [ [/^###### (.*)/gm, '<h6>$1</h6>'],
                         [/^##### (.*)/gm,  '<h5>$1</h5>'],
                         [/^#### (.*)/gm,   '<h4>$1</h4>'],
                         [/^### (.*)/gm,    '<h3>$1</h3>'],
                         [/^## (.*)/gm,     '<h2>$1</h2>'],
                         [/^# (.*)/gm,      '<h1>$1</h1>'] ]
                      );
  }
}

function transformQuotes(text, orig) {
  if (orig.match(/"|'/)) {
    return massReplace(text,
                       [ [/"(?=\b|\*|')/g,             '&ldquo;'],
                         [/"(?!\b|\*|')/g,             '&rdquo;'],
                         [/'(?!\b|\*)|(?=\b)'(?=\b)/g, '&lsquo;'],
                         [/'(?=\b|\*)/g,               '&rsquo;'] ]
                      );
  }
}

function transformStyling(text, orig) {
  if (orig.match(/\*\*|\*/)) {
    return massReplace(text, 
                       [ [ /\*\*([^\*|\s]+)\*\*/g, '<strong>$1</strong>'], 
                         [ /\*([^\*|\s]+)\*/g,     '<em>$1</em>' ] ]);
  }
}

function transformDashes(text, orig) {
  if (orig.match(/\-\-/)) {
    return massReplace (text, [ [ /\-\-/g, '&mdash;' ] ]);
  }
}

function transformParagraphs(text, orig) {
  if (! orig.match(/^#{1,6} (.*)/)) {
    return `<p>${text}</p>`;
  }
}

function transformToHTML(markdownSource) {
  let data = markdownSource.split('\n\n'),
      orig = data.slice(),
      transforms = [ transformHeadings, transformQuotes, transformDashes, 
                     transformStyling, transformParagraphs ];

  for (let i = 0, l = orig.length; i < l; ++i) {
    for (let transform of transforms) {
      let result;
      if ((result = transform(data[i], orig[i])) ! == undefined) {
        data[i] = result;
      }
    }
  }

  return data.join('\n');
}

NOTE: For engines that don't support destructuring (looking at you, io.js), use this method instead:
function massReplace(text, replacementArray) {
  let results = text;
  for (let replacementArrayItem of replacementArray) {
    let regex = replacementArrayItem[0],
        replacement = replacementArrayItem[1];
    results = results.replace(regex, replacement);
  }
  return results;
}

