src/parser.js

Summary

Maintainability
A
0 mins
Test Coverage
import { ATTR, TAG } from './node-types'
import { rootTagNotFound, unexpectedEndOfFile } from './messages'
import attr from './parsers/attribute'
import curry from 'curri'
import flush from './utils/flush-parser-state'
import panic from './utils/panic'
import tag from './parsers/tag'
import text from './parsers/text'
import treeBuilder from './tree-builder'

/**
 * Factory for the Parser class, exposing only the `parse` method.
 * The export adds the Parser class as property.
 *
 * @param   {Object}   options - User Options
 * @param   {Function} customBuilder - Tree builder factory
 * @returns {Function} Public Parser implementation.
 */
export default function parser(options, customBuilder) {
  const state = curry(createParserState)(options, customBuilder || treeBuilder)
  return {
    parse: (data) => parse(state(data)),
  }
}

/**
 * Create a new state object
 * @param   {Object} userOptions - parser options
 * @param   {Function} builder - Tree builder factory
 * @param   {string} data - data to parse
 * @returns {ParserState} it represents the current parser state
 */
function createParserState(userOptions, builder, data) {
  const options = Object.assign(
    {
      brackets: ['{', '}'],
    },
    userOptions,
  )

  return {
    options,
    regexCache: {},
    pos: 0,
    count: -1,
    root: null,
    last: null,
    scryle: null,
    builder: builder(data, options),
    data,
  }
}

/**
 * It creates a raw output of pseudo-nodes with one of three different types,
 * all of them having a start/end position:
 *
 * - TAG     -- Opening or closing tags
 * - TEXT    -- Raw text
 * - COMMENT -- Comments
 *
 * @param   {ParserState}  state - Current parser state
 * @returns {ParserResult} Result, contains data and output properties.
 */
function parse(state) {
  const { data } = state

  walk(state)
  flush(state)

  if (state.count) {
    panic(
      data,
      state.count > 0 ? unexpectedEndOfFile : rootTagNotFound,
      state.pos,
    )
  }

  return {
    data,
    output: state.builder.get(),
  }
}

/**
 * Parser walking recursive function
 * @param {ParserState}  state - Current parser state
 * @param {string} type - current parsing context
 * @returns {undefined} void function
 */
function walk(state, type) {
  const { data } = state
  // extend the state adding the tree builder instance and the initial data
  const length = data.length

  // The "count" property is set to 1 when the first tag is found.
  // This becomes the root and precedent text or comments are discarded.
  // So, at the end of the parsing count must be zero.
  if (state.pos < length && state.count) {
    walk(state, eat(state, type))
  }
}

/**
 * Function to help iterating on the current parser state
 * @param {ParserState}  state - Current parser state
 * @param   {string} type - current parsing context
 * @returns {string} parsing context
 */
function eat(state, type) {
  switch (type) {
    case TAG:
      return tag(state)
    case ATTR:
      return attr(state)
    default:
      return text(state)
  }
}