Source: parser.js

/** Parser module
 * @module Parser
 * @author Onien
 */

/**
 * @function parseVar
 * @desc parses and finds varible from given data
 * @param {string} token - varible token
 * @param {Object} data - data
 * @example
 *  // returns "hello"
 *  parseVar("data.1.really", {
 *    data: [
 *      "something",
 *      {
 *        really: "hello"
 *      }
 *    ]
 *  })
 *  @returns {*} value
 */
function parseVar(token, data) {
  let value;
  let errors = [];
  token.split(".").forEach((varible, idx) => {
    try {
      if (idx > 0) {
        value = value[varible];
      } else {
        value = data[varible];
      }
    } catch {
      return errors.push({
        type: "error",
        text: "error: varible " + varible + " not found!",
      });
    }
    if (value == undefined) {
      return errors.push({
        type: "error",
        text: "error: varible " + varible + " not found!",
      });
    }
  });
  if (errors.length > 0) return errors;
  return value;
}

/** @function Throw
 * @desc retuns error message
 * @example
 *  // returns "ERROR: message at 2! Substring wowo - 5 in token tokeeen"
 *  Throw("message", {
 *    idx: 2, substr: "wowo", substridx: 5, token: "tokeeen"
 *  })
 * @param {string} message
 * @param {Object} trace
 * @param {number} trace.idx
 * @param {string} trace.substr
 * @param {number} trace.substridx
 * @param {string} trace.token
 * @returns {string} message
 */
function Throw(message, trace) {
  return `ERROR: ${message} at ${trace.idx}! Substring ${trace.substr} - ${trace.substridx} in token ${trace.token}`;
}

/** @function generateHTML
 * @desc generates html without using dom methods
 * @example
 *  // returns <div style="background: green">hello</div>
 *  generateHTML(new Map([
 *    ["background", "green"],
 *    ["tag", "div"],
 *    ["text", "hello"]
 *  ]));
 * @param {Map} parsed
 * @returns {string} html
 */
function generateHTML(parsed) {
  let text;
  let styles = "";
  let errors = [];
  let tagname = parsed.get("tag") ?? "span";
  parsed.forEach((value, key) => {
    if (key == "text") {
      text = value;
    } else if (key == "color") {
      styles += "color: " + value + "; ";
    } else if (key == "background") {
      styles += "background: " + value + "; ";
    } else if (key == "tag");
    else {
      return errors.push({
        type: "error",
        text: `ERROR: Couldn't find "${key}" param`,
      });
    }
  });
  if (errors.length > 0) return errors;
  return `<${tagname}${
    styles.trim() != "" ? ' style="' + styles + '"' : ""
  }>${text}</${tagname}>`;
}

/** @function generateHTMLDOM
 * @desc generates html using dom
 * @see {@link generateHTML}
 * @returns {(string|Array.Object)} if dom isnt exists, it will return array of objects with errors
 */
function generateHTMLDOM(parsed) {
  try {
    let errors = [];
    let newElement = document.createElement(parsed.get("tag") ?? "span");
    parsed.forEach((value, key) => {
      if (key == "text") {
        newElement.innerText = value;
      } else if (key == "color") {
        newElement.style.color = value;
      } else if (key == "background") {
        newElement.style.background = value;
      } else if (key == "tag");
      else {
        return errors.push({
          type: "error",
          text: `ERROR: Couldn't find "${key}" param`,
        });
      }
    });
    if (errors.length > 0) return errors;
    return newElement.outerHTML;
  } catch {
    return [
      {
        type: "error",
        text: "Couldn't find DOM parser!",
      },
    ];
  }
}

/** @function parse
 * @desc parses text to html
 * @example
 *  // returns Hello, onien! I think you want to eat and to do .... Btw, <span style="color: red; ">this is the phrase</span>. Oh, <span style="background: green; ">green back?</span>
 *  parse("Hello, {name}! I think you want {wants.0} and {wants.1}. Btw, { text=phrase, color='red' }. Oh, { text='green back?', background='green' }", 
 *  {
 *    name: "onien",
 *    wants: [
 *      "to eat",
 *      "to do ..."
 *    ],
 *    phrase: "this is the phrase"
 *  })
 *  @param {string} str - string to parse
 *  @param {Object} data
 *  @param {Object} options
 *  @param {boolean} options.useDOM
 *  @returns {(string|string[])} parsed text (html) or array of errors
 */
export function parse(str, data, options = {}) {
  let splited = str.split(/(?<=})|(?=\{)/);
  let errors = [];
  let newValue = "";
  splited.forEach((substr, substridx) => {
    if (substr.startsWith("{")) {
      let token = substr.slice(1, -1);
      let parsed = new Map();
      if (token.split(",").length != 1) {
        token.split(",").forEach((arg, idx) => {
          if (arg.split("=").length == 1) {
            return errors.push(
              Throw("Expected value", {
                substr: substr,
                substridx: substridx,
                idx: idx,
                token: token,
              })
            );
          }
          let argname = arg.split("=")[0];
          let argvalue = arg.split("=")[1];

          // -------------- checking errors ----------

          if (argvalue.trim() == "") {
            return errors.push(
              Throw("Expected non-empty value", {
                substr: substr,
                substridx: substridx,
                idx: idx,
                token: token,
              })
            );
          }
          if (argname.trim() == "") {
            return errors.push(
              Throw("Expected non-empty argname", {
                substr: substr,
                substridx: substridx,
                idx: idx,
                token: token,
              })
            );
          }

          // ---------- value process ----------

          if (argvalue.trim().startsWith("'")) {
            parsed.set(argname.trim(), argvalue.trim().slice(1, -1));
          } else {
            let varible = parseVar(argvalue.trim(), data);
            if (typeof varible == "object" && varible[0].type == "error") {
              return errors.push(varible[0].text);
            }
            parsed.set(argname.trim(), varible);
          }
        });
      } else {
        let varible = parseVar(token.trim(), data);
        if (typeof varible == "object" && varible[0].type == "error") {
          return errors.push(varible[0].text);
        }
        parsed.set("__VALUE", varible);
      }

      if (parsed.has("__VALUE")) substr = parsed.get("__VALUE");
      else {
        let generated;
        if (options.useDOM == undefined || options.useDOM == true) {
          generated = generateHTMLDOM(parsed);
        } else {
          generated = generateHTML(parsed);
        }
        if (typeof generated == "object" && generated[0].type == "error") {
          return errors.push(generated[0].text);
        }
        substr = generated;
      }
    }
    newValue += substr;
  });
  if (errors.length > 0) {
    return errors;
  }
  return newValue;
}