all files / dom/ parseMarkup.js

91.53% Statements 54/59
73.08% Branches 19/26
100% Functions 14/14
92.98% Lines 53/57
1 statement, 1 branch Ignored     
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136                        485×   485×   485×     485× 485× 485× 484×                     485× 485× 485×         485× 485×         484×                 2051×       3154× 3154× 3154×   3154×   3154× 3154× 1376× 1376×   1778×     3154× 3154×       2053× 2053× 1328×   2053× 2053×       1089×     1089× 1089× 1089× 1089×     1089× 1089×                                            
import { forEach } from '../util'
import ElementType from 'domelementtype'
import Parser from '../vendor/htmlparser2'
import MemoryDOMElement from './MemoryDOMElement'
 
/*
  Parses HTML or XML
 
  Options:
  - format: 'html' or 'xml'
  - ownerDocument: an MemoryDOMElement instance of type 'document'
*/
export default function parseMarkup(markup, options) {
  let format = options.ownerDocument ? options.ownerDocument.format : options.format
  /* istanbul ignore next */
  Iif (!format) {
    throw new Error("Either 'ownerDocument' or 'format' must be set.")
  }
  let parserOptions = {
    xmlMode : (format === 'xml')
  }
  let handler = new DomHandler({ format })
  let parser = new Parser(handler, parserOptions)
  parser.end(markup)
  return handler.document
}
 
 
const re_whitespace = /\s+/g
 
/*
  Customized implementation of [DomHandler](https://github.com/fb55/domhandler).
*/
class DomHandler {
 
  constructor(options = {}) {
    this.options = options
    this.document = null
    this._tagStack = []
  }
 
  // called directly after construction of Parser and at the end of Parser.reset()
  onparserinit(){
    this.document = new MemoryDOMElement('document', { format: this.options.format })
    this._tagStack = [this.document]
  }
 
  onend(){
    // TODO: would be nice to generate a good error message
    Iif (this._tagStack.length>1) {
      throw new Error(`Unexpected EOF. Tag was opened but not closed.`)
    }
  }
 
  onerror(error) {
    throw new Error(error)
  }
 
  onclosetag() {
    this._tagStack.pop()
  }
 
  _addDomElement(element) {
    let parent = this._tagStack[this._tagStack.length - 1]
    Iif (!parent.childNodes) parent.childNodes = []
    let siblings = parent.childNodes
 
    let previousSibling = siblings[siblings.length - 1]
    // set up next/previous link
    element.next = null
    if(previousSibling){
      element.prev = previousSibling
      previousSibling.next = element
    } else {
      element.prev = null
    }
    // either push the element to the current open tag's children, or keep a reference as top-level element
    siblings.push(element)
    element.parent = parent || null
  }
 
  onopentag(name, attributes) {
    let element = this.document.createElement(name)
    forEach(attributes, (val, key) => {
      element.setAttribute(key, val)
    })
    this._addDomElement(element)
    this._tagStack.push(element)
  }
 
  ontext(text) {
    Iif (this.options.normalizeWhitespace) {
      text = text.replace(re_whitespace, " ")
    }
    let lastTag
    let _top = this._tagStack[this._tagStack.length - 1]
    Eif (_top && _top.childNodes) lastTag = _top.childNodes[_top.childNodes.length - 1]
    Iif (lastTag && lastTag.type === ElementType.Text) {
      lastTag.data += text
    } else {
      let element = this.document.createTextNode(text)
      this._addDomElement(element)
    }
  }
 
  oncomment(data) {
    var lastTag = this._tagStack[this._tagStack.length - 1]
    Iif(lastTag && lastTag.type === ElementType.Comment){
      lastTag.data += data
    } else {
      let element = this.document.createComment(data)
      this._addDomElement(element)
      this._tagStack.push(element)
    }
  }
 
  oncommentend() {
    this._tagStack.pop()
  }
 
  oncdatastart(data) {
    let element = this.document.createCDATASection(data)
    this._addDomElement(element)
    this._tagStack.push(element)
  }
 
  oncdataend() {
    this._tagStack.pop()
  }
 
  onprocessinginstruction(name, data) {
    let element = this.document.createProcessingInstruction(name, data)
    this._addDomElement(element)
  }
 
}