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 &lt; tree.length; ++i) {
    // turn #s into heading tags if #s are present
    if (tree[i].match(/^#/g)) {
      data[i] = data[i]
        .replace(/^#### (.*)/gm, '&lt;h4&gt;$1&lt;/h4&gt;')
        .replace(/^### (.*)/gm, '&lt;h3&gt;$1&lt;/h3&gt;')
        .replace(/^## (.*)/gm, '&lt;h2&gt;$1&lt;/h2&gt;')
        .replace(/^# (.*)/gm, '&lt;h1&gt;$1&lt;/h1&gt;')
    }

    // 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, '&lt;strong&gt;$1&lt;/strong&gt;')
        .replace(/\*([^\*|\s]+)\*/g, '&lt;em&gt;$1&lt;/em&gt;')
    }

    // 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] &amp;&amp; (tree[i - 1].match(/^#/g) || tree[i - 1] === "* * *")) {
        data[i] = '&lt;p class="ni"&gt;' + data[i] + '&lt;/p&gt;'
      } else {
        data[i] = '&lt;p&gt;' + data[i] + '&lt;/p&gt;'
      }
    }
  }

  // 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:

&lt;h1&gt;Title&lt;/h1&gt;

&lt;p class="ni"&gt;‘Single quotes’&lt;/p&gt;

&lt;p&gt;“Double Quotes”&lt;/p&gt;

&lt;p&gt;* * *&lt;/p&gt;

&lt;p class="ni"&gt;Paragraphs
&lt;/p&gt;


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?
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, '&lt;h6&gt;$1&lt;/h6&gt;'],
                         [/^##### (.*)/gm,  '&lt;h5&gt;$1&lt;/h5&gt;'],
                         [/^#### (.*)/gm,   '&lt;h4&gt;$1&lt;/h4&gt;'],
                         [/^### (.*)/gm,    '&lt;h3&gt;$1&lt;/h3&gt;'],
                         [/^## (.*)/gm,     '&lt;h2&gt;$1&lt;/h2&gt;'],
                         [/^# (.*)/gm,      '&lt;h1&gt;$1&lt;/h1&gt;'] ]
                      );
  }
}

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

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

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

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

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 &lt; 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;
}