Called on match `<`
function consume_opening_or_closing_tag (proc_stack) {
  proc_stack.tSkip();
  let is_closing = proc_stack.tSkipIf("/");
  let tag_name = proc_stack.tAcceptUntilSet(TAG_NAME_DELIMITER).toLocaleLowerCase();
  if (!/^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$/.test(tag_name)) {
    throw new SyntaxError(`Invalid HTML tag name "${tag_name}"`);
  }

  if (!is_closing && /^zc-/.test(tag_name)) {
    return consume_directive_tag(proc_stack, tag_name);
  }

  let parts = []; // Parts are needed for attribute value directives

  let html = "<" + (is_closing ? "/" : "") + tag_name;
  let done = false;
  while (!done) {
    html += proc_stack.tAcceptUntilSet(ATTR_VAL_START_OR_TAG_END_DELIMITER);
    let c = proc_stack.tAccept();
    html += c;

    switch (c) {
    case "\"":
      parts.push(html);
      Array.prototype.push.apply(parts, consume_char_data_with_entity_directives(proc_stack, false, "\""));
      html = proc_stack.tAccept();
      break;

    case ">":
      done = true;
      break;

    default:
      throw new Error(`INTERR Invalid char after ATTR_VAL_START_OR_TAG_END_DELIMITER`);
    }
  }

  parts.push(html);

  return parts;
}