' +
              'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
              'Check your browser\'s JavaScript console for more details.' +
              'Remember that you need to serve the presentation HTML from a HTTP server.
' +
              ''
            }
          }
        }
        xhr.open('GET', url, false)
        try {
          xhr.send()
        } catch (e) {
          alert('Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e)
        }
      } else if (section.getAttribute('data-separator') || section.getAttribute('data-separator-vertical') || section.getAttribute('data-separator-notes')) {
        section.outerHTML = slidify(getMarkdownFromSlide(section), {
          separator: section.getAttribute('data-separator'),
          verticalSeparator: section.getAttribute('data-separator-vertical'),
          notesSeparator: section.getAttribute('data-separator-notes'),
          attributes: getForwardedAttributes(section)
        })
      } else {
        section.innerHTML = createMarkdownSlide(getMarkdownFromSlide(section))
      }
    }
  }
  /**
   * Check if a node value has the attributes pattern.
   * If yes, extract it and add that value as one or several attributes
   * the the terget element.
   *
   * You need Cache Killer on Chrome to see the effect on any FOM transformation
   * directly on refresh (F5)
   * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
   */
  function addAttributeInElement (node, elementTarget, separator) {
    var mardownClassesInElementsRegex = new RegExp(separator, 'mg')
    var mardownClassRegex = new RegExp('([^"= ]+?)="([^"=]+?)"', 'mg')
    var nodeValue = node.nodeValue
    var matches
    var matchesClass
    if ((matches = mardownClassesInElementsRegex.exec(nodeValue))) {
      var classes = matches[1]
      nodeValue = nodeValue.substring(0, matches.index) + nodeValue.substring(mardownClassesInElementsRegex.lastIndex)
      node.nodeValue = nodeValue
      while ((matchesClass = mardownClassRegex.exec(classes))) {
        var name = matchesClass[1]
        var value = matchesClass[2]
        if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, window.filterXSS.escapeAttrValue(value)) }
      }
      return true
    }
    return false
  }
  /**
   * Add attributes to the parent element of a text node,
   * or the element of an attribute node.
   */
  function addAttributes (section, element, previousElement, separatorElementAttributes, separatorSectionAttributes) {
    if (element != null && element.childNodes !== undefined && element.childNodes.length > 0) {
      var previousParentElement = element
      for (var i = 0; i < element.childNodes.length; i++) {
        var childElement = element.childNodes[i]
        if (i > 0) {
          let j = i - 1
          while (j >= 0) {
            var aPreviousChildElement = element.childNodes[j]
            if (typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName !== 'BR') {
              previousParentElement = aPreviousChildElement
              break
            }
            j = j - 1
          }
        }
        var parentSection = section
        if (childElement.nodeName === 'section') {
          parentSection = childElement
          previousParentElement = childElement
        }
        if (typeof childElement.setAttribute === 'function' || childElement.nodeType === Node.COMMENT_NODE) {
          addAttributes(parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes)
        }
      }
    }
    if (element.nodeType === Node.COMMENT_NODE) {
      if (addAttributeInElement(element, previousElement, separatorElementAttributes) === false) {
        addAttributeInElement(element, section, separatorSectionAttributes)
      }
    }
  }
  /**
   * Converts any current data-markdown slides in the
   * DOM to HTML.
   */
  function convertSlides () {
    var sections = document.querySelectorAll('[data-markdown]')
    for (var i = 0, len = sections.length; i < len; i++) {
      var section = sections[i]
      // Only parse the same slide once
      if (!section.getAttribute('data-markdown-parsed')) {
        section.setAttribute('data-markdown-parsed', true)
        var notes = section.querySelector('aside.notes')
        var markdown = getMarkdownFromSlide(section)
        var rendered = md.render(markdown)
        rendered = preventXSS(rendered)
        var result = window.postProcess(rendered)
        section.innerHTML = result[0].outerHTML
        addAttributes(section, section, null, section.getAttribute('data-element-attributes') ||
        section.parentNode.getAttribute('data-element-attributes') ||
        DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
        section.getAttribute('data-attributes') ||
        section.parentNode.getAttribute('data-attributes') ||
        DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR)
        // If there were notes, we need to re-add them after
        // having overwritten the section's HTML
        if (notes) {
          section.appendChild(notes)
        }
      }
    }
  }
  // API
  return {
    initialize: function () {
      processSlides()
      convertSlides()
    },
    // TODO: Do these belong in the API?
    processSlides: processSlides,
    convertSlides: convertSlides,
    slidify: slidify
  }
}))