All files HtmlParser.class.js

100% Statements 125/125
100% Branches 55/55
100% Functions 17/17
100% Lines 112/112

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 1651x 1x 1x   39x 39x 39x 39x 39x   39x 81x     39x 748x     39x   81x 70x 70x 70x   11x 11x     39x 89x 89x 70x 46x     39x 266x 230x 228x 31x 31x 31x   197x 197x     39x 95x 61x 53x 8x 8x 8x   45x 45x     39x 242x 222x 4x 4x   218x 177x 177x     39x 48x 41x 41x 28x 16x 1x 1x 1x 1x   15x     39x 73x 13x 13x     60x 60x     39x 68x 12x 12x     56x 56x     39x 72x 8x 8x   64x 4x 4x     60x 3x 3x 3x 3x   57x 57x     39x 25x 21x 4x 4x 4x   17x     39x 23x 22x 22x 22x 22x   1x     39x 25x 24x 1x 1x 1x   23x 23x     39x     961x     39x    
const { Writable } = require("node:stream")
const Token = require('./Token.class')
module.exports = class HtmlParser extends Writable {
  constructor() {
    super()
    let currentToken = null
    let currentAttribueName = ''
    let currentAttribueValue = ''
    let currentTagName = ''
 
    const emit = token => {
      this.emit('data', token.getLog())
    }
 
    const isWhiteSpace = char => {
      return char === ' ' || char === '\t' || char === '\n'
    }
 
    const dataState = char => {
      // if (char === '&') return dataState
      if (char === '<') {
        currentToken = new Token()
        currentToken.setType('openTag')
        return tagOpen 
      }
      emit(new Token('char', char))
      return dataState
    }
 
    const tagOpen = char => {
      currentTagName = ''
      if (char === ' ') return tagOpen
      if (char === '/') return tagEnd
      return tagName(char) // reconsume
    }
 
    const tagName = char => {
      if (isWhiteSpace(char)) return beforeAttribute
      if (char === '/') return selfClosingStartTag
      if (char === '>') {
        currentToken.setContent(currentTagName)
        emit(currentToken)
        return dataState
      }
      currentTagName += char
      return tagName 
    }
 
    const beforeAttribute = char => {
      if (isWhiteSpace(char)) return beforeAttribute
      if (char === '/') return selfClosingStartTag
      if (char === '>') {
        currentToken.setContent(currentTagName)
        emit(currentToken)
        return dataState
      }
      currentAttribueName = ''
      return attributeName(char)
    }
 
    const attributeName = char => {
      if (isWhiteSpace(char)) return attributeName
      if (char === '/') {
        currentToken.setAttribute(currentAttribueName, currentAttribueName)
        return selfClosingStartTag
      }
      if (char === '=') return beforeAttributeValue
      currentAttribueName += char
      return attributeName
    }
 
    const beforeAttributeValue = char => {
      if (isWhiteSpace(char)) return beforeAttributeValue
      currentAttribueValue = ''
      if (char === '"') return attributeValueDouble
      if (char === `'`) return attributeValueSingle
      if (char === '>') {
        currentToken.setAttribute(currentAttribueName, currentAttribueValue)
        currentToken.setContent(currentTagName)
        emit(currentToken)
        return dataState
      }
      return attributeValueUnquoted(char)
    }
 
    const attributeValueDouble = char => {
      if (char === '"') {
        currentToken.setAttribute(currentAttribueName, currentAttribueValue)
        return afterAttributeValueQuoted
      }
      // if (char === '&') return attributeValueDouble
      currentAttribueValue += char
      return attributeValueDouble
    }
 
    const attributeValueSingle = char => {
      if (char === `'`) {
        currentToken.setAttribute(currentAttribueName, currentAttribueValue)
        return afterAttributeValueQuoted
      }
      // if (char === '&') return attributeValueSingle
      currentAttribueValue += char
      return attributeValueSingle
    }
 
    const attributeValueUnquoted = char => {
      if (isWhiteSpace(char)) {
        currentToken.setAttribute(currentAttribueName, currentAttribueValue)
        return beforeAttribute
      }
      if (char === '/') {
        currentToken.setAttribute(currentAttribueName, currentAttribueValue)
        return selfClosingStartTag
      }
      // if (char === '&') return attributeValueUnquoted
      if (char === '>') {
        currentToken.setAttribute(currentAttribueName, currentAttribueValue)
        currentToken.setContent(currentTagName)
        emit(currentToken)
        return dataState 
      }
      currentAttribueValue += char
      return attributeValueUnquoted
    }
 
    const afterAttributeValueQuoted = char => {
      if (char === '/') return selfClosingStartTag
      if (char === '>') {
        currentToken.setContent(currentTagName)
        emit(currentToken)
        return dataState
      }
      return beforeAttribute(char)
    }
 
    const selfClosingStartTag = char => {
      if (char === '>') {
        currentToken.setType('selfClosingTag')
        currentToken.setContent(currentTagName)
        emit(currentToken)
        return dataState
      }
      return selfClosingStartTag
    }
    
    const tagEnd = char => {
      if (isWhiteSpace(char)) return tagEnd
      if (char === '>') {
        currentToken = new Token('closeTag')
        emit(currentToken)
        return dataState
      }
      currentToken.setType('closeTag')
      return tagName(char)
    }
 
    this.state = dataState
  }
  write(char) {
    this.state = this.state(char)
  }
  end() {
    this.emit('end')
  }
}